serverside 0.2.6 → 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/CHANGELOG +22 -16
  2. data/Rakefile +11 -4
  3. data/bin/serverside +1 -1
  4. data/doc/rdoc/classes/Daemon.html +4 -4
  5. data/doc/rdoc/classes/ServerSide.html +10 -10
  6. data/doc/rdoc/classes/ServerSide/HTTP.html +5 -6
  7. data/doc/rdoc/classes/ServerSide/HTTP/Connection.html +24 -22
  8. data/doc/rdoc/classes/ServerSide/HTTP/Request.html +367 -121
  9. data/doc/rdoc/classes/ServerSide/Router.html +58 -58
  10. data/doc/rdoc/classes/ServerSide/StaticFiles.html +60 -49
  11. data/doc/rdoc/classes/ServerSide/StaticFiles/Const.html +12 -2
  12. data/doc/rdoc/classes/ServerSide/Template.html +12 -12
  13. data/doc/rdoc/created.rid +1 -1
  14. data/doc/rdoc/files/CHANGELOG.html +36 -31
  15. data/doc/rdoc/files/COPYING.html +1 -1
  16. data/doc/rdoc/files/README.html +1 -1
  17. data/doc/rdoc/files/lib/serverside/application_rb.html +1 -1
  18. data/doc/rdoc/files/lib/serverside/cluster_rb.html +1 -1
  19. data/doc/rdoc/files/lib/serverside/connection_rb.html +1 -1
  20. data/doc/rdoc/files/lib/serverside/core_ext_rb.html +1 -1
  21. data/doc/rdoc/files/lib/serverside/daemon_rb.html +1 -1
  22. data/doc/rdoc/files/lib/serverside/request_rb.html +8 -1
  23. data/doc/rdoc/files/lib/serverside/routing_rb.html +1 -1
  24. data/doc/rdoc/files/lib/serverside/server_rb.html +1 -1
  25. data/doc/rdoc/files/lib/serverside/static_rb.html +8 -1
  26. data/doc/rdoc/files/lib/serverside/template_rb.html +1 -1
  27. data/doc/rdoc/files/lib/serverside_rb.html +1 -1
  28. data/doc/rdoc/fr_class_index.html +0 -1
  29. data/doc/rdoc/fr_method_index.html +21 -20
  30. data/lib/serverside/connection.rb +6 -4
  31. data/lib/serverside/request.rb +101 -61
  32. data/lib/serverside/static.rb +22 -7
  33. data/test/functional/request_body_test.rb +93 -0
  34. data/test/functional/routing_server.rb +5 -5
  35. data/test/functional/routing_server_test.rb +3 -3
  36. data/test/spec/connection_spec.rb +61 -0
  37. data/test/spec/core_ext_spec.rb +20 -1
  38. data/test/unit/connection_test.rb +9 -9
  39. data/test/unit/request_test.rb +55 -55
  40. data/test/unit/server_test.rb +1 -1
  41. data/test/unit/static_test.rb +24 -25
  42. metadata +60 -60
  43. data/doc/rdoc/classes/ServerSide/HTTP/Const.html +0 -257
data/doc/rdoc/created.rid CHANGED
@@ -1 +1 @@
1
- Sat Sep 30 23:13:26 IDT 2006
1
+ Wed Oct 11 10:16:55 IST 2006
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr class="top-aligned-row">
58
58
  <td><strong>Last Update:</strong></td>
59
- <td>Sat Sep 30 23:07:59 IDT 2006</td>
59
+ <td>Wed Oct 11 10:16:23 IST 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -69,9 +69,38 @@
69
69
  <div id="contextContent">
70
70
 
71
71
  <div id="description">
72
- <p>
73
- <b>SVN</b>
74
- </p>
72
+ <h2>0.2.7</h2>
73
+ <ul>
74
+ <li>Wrote spec for HTTP::Connection.
75
+
76
+ </li>
77
+ <li>Added spec files to rake stats reporting.
78
+
79
+ </li>
80
+ <li>Changed @conn to @socket in both HTTP::Connection and HTTP::Request for
81
+ better readability.
82
+
83
+ </li>
84
+ <li>Wrote functional test for request body (but at least some of the testing
85
+ should be in a unit test.)
86
+
87
+ </li>
88
+ <li>Added request body parsing (both URL-encoded and multipart.)
89
+
90
+ </li>
91
+ <li>Moved all HTTP::Const constants into HTTP::Request.
92
+
93
+ </li>
94
+ <li>Added Date to response headers. This is needed for caching to work
95
+ correctly.
96
+
97
+ </li>
98
+ <li>Fixed bug in serverside script that caused an exception when specifying
99
+ port number.
100
+
101
+ </li>
102
+ </ul>
103
+ <h2>0.2.6</h2>
75
104
  <ul>
76
105
  <li>Refactored HTTP-related code into a new HTTP::Request class and a
77
106
  simplified HTTP::Connection.
@@ -88,9 +117,7 @@ the docs.
88
117
 
89
118
  </li>
90
119
  </ul>
91
- <p>
92
- *0.2.5*
93
- </p>
120
+ <h2>0.2.5</h2>
94
121
  <ul>
95
122
  <li>Added template serving to static file module.
96
123
 
@@ -124,9 +151,7 @@ test task to include spec and rcov tasks.
124
151
 
125
152
  </li>
126
153
  </ul>
127
- <p>
128
- *0.2.0*
129
- </p>
154
+ <h2>0.2.0</h2>
130
155
  <ul>
131
156
  <li>Updated RFuzz script to work, but it doesn&#8217;t still do anything
132
157
  interesting.
@@ -228,38 +253,21 @@ core_ext.
228
253
  </li>
229
254
  <li>Added HTTP parsing code with unit tests.
230
255
 
231
- </li>
232
- <li>Started adding request code.
233
-
234
256
  </li>
235
257
  <li>More unit tests for application code.
236
258
 
237
259
  </li>
238
260
  <li>Basic server code works with unit tests.
239
261
 
240
- </li>
241
- <li>Started work on application code.
242
-
243
262
  </li>
244
263
  <li>Added option parsing to serverside script.
245
264
 
246
265
  </li>
247
266
  <li>Added daemon code and unit tests.
248
267
 
249
- </li>
250
- <li>Created serverside script.
251
-
252
- </li>
253
- <li>Created Gem spec.
254
-
255
- </li>
256
- <li>Created directory structure.
257
-
258
268
  </li>
259
269
  </ul>
260
- <p>
261
- *Mongrel-based branch*
262
- </p>
270
+ <h2>0.1</h2>
263
271
  <ul>
264
272
  <li>Added host attribute to Controller::Request.
265
273
 
@@ -311,9 +319,6 @@ core_ext.
311
319
  </li>
312
320
  <li>Implemented daemon and server cluster.
313
321
 
314
- </li>
315
- <li>Basic configuration infrastructure is ready.
316
-
317
322
  </li>
318
323
  </ul>
319
324
 
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr class="top-aligned-row">
58
58
  <td><strong>Last Update:</strong></td>
59
- <td>Thu Aug 17 18:37:26 IDT 2006</td>
59
+ <td>Wed Aug 16 11:52:20 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>Sat Sep 30 23:10:14 IDT 2006</td>
59
+ <td>Sat Sep 30 23:36:42 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>Sat Sep 30 23:04:45 IDT 2006</td>
59
+ <td>Sat Sep 30 23:36:41 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>Thu Aug 24 18:47:24 IDT 2006</td>
59
+ <td>Mon Aug 21 10:29:28 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>Sat Sep 30 23:06:05 IDT 2006</td>
59
+ <td>Sun Oct 08 08:43:13 IST 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>Fri Aug 25 16:38:37 IDT 2006</td>
59
+ <td>Sun Aug 27 10:06:32 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 01:03:58 IDT 2006</td>
59
+ <td>Mon Aug 28 10:38:28 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>Sat Sep 30 22:55:32 IDT 2006</td>
59
+ <td>Sun Oct 08 08:44:21 IST 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -69,6 +69,13 @@
69
69
  <div id="contextContent">
70
70
 
71
71
 
72
+ <div id="requires-list">
73
+ <h3 class="section-bar">Required files</h3>
74
+
75
+ <div class="name-list">
76
+ time&nbsp;&nbsp;
77
+ </div>
78
+ </div>
72
79
 
73
80
  </div>
74
81
 
@@ -56,7 +56,7 @@
56
56
  </tr>
57
57
  <tr class="top-aligned-row">
58
58
  <td><strong>Last Update:</strong></td>
59
- <td>Sat Sep 30 22:47:12 IDT 2006</td>
59
+ <td>Sat Sep 30 23:36:41 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>Sat Sep 30 23:04:12 IDT 2006</td>
59
+ <td>Sat Sep 30 23:36:41 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>Sat Sep 30 22:55:21 IDT 2006</td>
59
+ <td>Sun Oct 08 08:55:40 IST 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -69,6 +69,13 @@
69
69
  <div id="contextContent">
70
70
 
71
71
 
72
+ <div id="requires-list">
73
+ <h3 class="section-bar">Required files</h3>
74
+
75
+ <div class="name-list">
76
+ time&nbsp;&nbsp;
77
+ </div>
78
+ </div>
72
79
 
73
80
  </div>
74
81
 
@@ -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 Sep 04 18:10:56 IDT 2006</td>
59
+ <td>Sun Sep 03 11:33:15 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>Thu Aug 24 18:47:25 IDT 2006</td>
59
+ <td>Mon Aug 21 10:57:08 IDT 2006</td>
60
60
  </tr>
61
61
  </table>
62
62
  </div>
@@ -31,7 +31,6 @@
31
31
  <a href="classes/ServerSide/Application.html">ServerSide::Application</a><br />
32
32
  <a href="classes/ServerSide/HTTP.html">ServerSide::HTTP</a><br />
33
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
34
  <a href="classes/ServerSide/HTTP/Request.html">ServerSide::HTTP::Request</a><br />
36
35
  <a href="classes/ServerSide/HTTP/Server.html">ServerSide::HTTP::Server</a><br />
37
36
  <a href="classes/ServerSide/Router.html">ServerSide::Router</a><br />
@@ -21,55 +21,56 @@
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/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 />
24
+ <a href="classes/ServerSide/Router.html#M000050">cache_constant (ServerSide::Router)</a><br />
25
+ <a href="classes/ServerSide/Router.html#M000046">compile_rules (ServerSide::Router)</a><br />
26
+ <a href="classes/ServerSide/Router.html#M000048">condition_part (ServerSide::Router)</a><br />
27
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
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 />
32
+ <a href="classes/ServerSide/Router.html#M000053">default_handler (ServerSide::Router)</a><br />
33
+ <a href="classes/ServerSide/Router.html#M000049">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/HTTP/Request.html#M000038">delete_cookie (ServerSide::HTTP::Request)</a><br />
35
+ <a href="classes/ServerSide/HTTP/Request.html#M000039">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/Router.html#M000043">has_routes? (ServerSide::Router)</a><br />
37
+ <a href="classes/ServerSide/Router.html#M000044">has_routes? (ServerSide::Router)</a><br />
38
38
  <a href="classes/ServerSide/HTTP/Request.html#M000029">new (ServerSide::HTTP::Request)</a><br />
39
+ <a href="classes/ServerSide/HTTP/Connection.html#M000040">new (ServerSide::HTTP::Connection)</a><br />
39
40
  <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
41
  <a href="classes/ServerSide/HTTP/Request.html#M000031">parse (ServerSide::HTTP::Request)</a><br />
42
+ <a href="classes/ServerSide/HTTP/Request.html#M000034">parse_body (ServerSide::HTTP::Request)</a><br />
42
43
  <a href="classes/ServerSide/HTTP/Request.html#M000033">parse_cookies (ServerSide::HTTP::Request)</a><br />
43
44
  <a href="classes/ServerSide/HTTP/Request.html#M000032">parse_parameters (ServerSide::HTTP::Request)</a><br />
44
45
  <a href="classes/Daemon/Base.html#M000012">pid_fn (Daemon::Base)</a><br />
45
46
  <a href="classes/Proc.html#M000002">proc_tag (Proc)</a><br />
46
47
  <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 />
48
+ <a href="classes/ServerSide/HTTP/Connection.html#M000041">process (ServerSide::HTTP::Connection)</a><br />
48
49
  <a href="classes/Daemon/PidFile.html#M000011">recall (Daemon::PidFile)</a><br />
49
50
  <a href="classes/Daemon/Cluster/PidFile.html#M000021">recall_pids (Daemon::Cluster::PidFile)</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 />
51
+ <a href="classes/ServerSide/HTTP/Request.html#M000036">redirect (ServerSide::HTTP::Request)</a><br />
52
+ <a href="classes/ServerSide/Template.html#M000043">render (ServerSide::Template)</a><br />
53
+ <a href="classes/ServerSide/Router.html#M000045">route (ServerSide::Router)</a><br />
54
+ <a href="classes/ServerSide/Router.html#M000051">route_default (ServerSide::Router)</a><br />
55
+ <a href="classes/ServerSide/Router.html#M000047">rule_to_statement (ServerSide::Router)</a><br />
56
+ <a href="classes/ServerSide/HTTP/Request.html#M000035">send_response (ServerSide::HTTP::Request)</a><br />
56
57
  <a href="classes/ServerSide/StaticFiles.html#M000023">serve_dir (ServerSide::StaticFiles)</a><br />
57
58
  <a href="classes/ServerSide/StaticFiles.html#M000022">serve_file (ServerSide::StaticFiles)</a><br />
58
59
  <a href="classes/ServerSide/StaticFiles.html#M000025">serve_static (ServerSide::StaticFiles)</a><br />
59
60
  <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 />
61
+ <a href="classes/ServerSide/Template.html#M000042">set (ServerSide::Template)</a><br />
62
+ <a href="classes/ServerSide/HTTP/Request.html#M000038">set_cookie (ServerSide::HTTP::Request)</a><br />
62
63
  <a href="classes/Daemon.html#M000008">start (Daemon)</a><br />
63
64
  <a href="classes/Daemon/Cluster.html#M000017">start (Daemon::Cluster)</a><br />
64
65
  <a href="classes/Daemon/Cluster.html#M000014">start_servers (Daemon::Cluster)</a><br />
65
- <a href="classes/Daemon/Cluster.html#M000018">stop (Daemon::Cluster)</a><br />
66
66
  <a href="classes/Daemon.html#M000009">stop (Daemon)</a><br />
67
+ <a href="classes/Daemon/Cluster.html#M000018">stop (Daemon::Cluster)</a><br />
67
68
  <a href="classes/Daemon/Cluster.html#M000015">stop_servers (Daemon::Cluster)</a><br />
68
69
  <a href="classes/Daemon/PidFile.html#M000010">store (Daemon::PidFile)</a><br />
69
70
  <a href="classes/Daemon/Cluster/PidFile.html#M000020">store_pid (Daemon::Cluster::PidFile)</a><br />
70
- <a href="classes/ServerSide/HTTP/Request.html#M000036">stream (ServerSide::HTTP::Request)</a><br />
71
+ <a href="classes/ServerSide/HTTP/Request.html#M000037">stream (ServerSide::HTTP::Request)</a><br />
71
72
  <a href="classes/Symbol.html#M000001">to_s (Symbol)</a><br />
72
- <a href="classes/ServerSide/Router.html#M000051">unhandled (ServerSide::Router)</a><br />
73
+ <a href="classes/ServerSide/Router.html#M000052">unhandled (ServerSide::Router)</a><br />
73
74
  <a href="classes/String.html#M000004">uri_escape (String)</a><br />
74
75
  <a href="classes/String.html#M000005">uri_unescape (String)</a><br />
75
76
  </div>
@@ -10,8 +10,8 @@ module ServerSide
10
10
  class Connection
11
11
  # Initializes the request instance. A new thread is created for
12
12
  # processing requests.
13
- def initialize(conn, request_class)
14
- @conn, @request_class = conn, request_class
13
+ def initialize(socket, request_class)
14
+ @socket, @request_class = socket, request_class
15
15
  @thread = Thread.new {process}
16
16
  end
17
17
 
@@ -20,12 +20,14 @@ module ServerSide
20
20
  # is closed.
21
21
  def process
22
22
  while true
23
- break unless @request_class.new(@conn).process
23
+ # the process function is expected to return true or a non-nil value
24
+ # if the connection is to persist.
25
+ break unless @request_class.new(@socket).process
24
26
  end
25
27
  rescue => e
26
28
  # We don't care. Just close the connection.
27
29
  ensure
28
- @conn.close
30
+ @socket.close
29
31
  end
30
32
  end
31
33
  end
@@ -1,58 +1,59 @@
1
1
  require File.join(File.dirname(__FILE__), 'static')
2
+ require 'time'
2
3
 
3
4
  module ServerSide
4
5
  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
6
+ # The Request class encapsulates HTTP requests. The request class
40
7
  # contains methods for parsing the request and rendering a response.
41
8
  # HTTP requests are created by the connection. Descendants of HTTPRequest
42
9
  # can be created
43
10
  # When a connection is created, it creates new requests in a loop until
44
11
  # the connection is closed.
45
12
  class Request
13
+
14
+ LINE_BREAK = "\r\n".freeze
15
+ # Here's a nice one - parses the first line of a request.
16
+ # The expected format is as follows:
17
+ # <method> </path>[/][?<query>] HTTP/<version>
18
+ REQUEST_REGEXP = /([A-Za-z0-9]+)\s(\/[^\/\?]*(?:\/[^\/\?]+)*)\/?(?:\?(.*))?\sHTTP\/(.+)\r/.freeze
19
+ # Regexp for parsing headers.
20
+ HEADER_REGEXP = /([^:]+):\s?(.*)\r\n/.freeze
21
+ CONTENT_LENGTH = 'Content-Length'.freeze
22
+ VERSION_1_1 = '1.1'.freeze
23
+ CONNECTION = 'Connection'.freeze
24
+ CLOSE = 'close'.freeze
25
+ AMPERSAND = '&'.freeze
26
+ # Regexp for parsing URI parameters.
27
+ PARAMETER_REGEXP = /(.+)=(.*)/.freeze
28
+ EQUAL_SIGN = '='.freeze
29
+ STATUS_CLOSE = "HTTP/1.1 %d\r\nDate: %s\r\nConnection: close\r\nContent-Type: %s\r\n%s%sContent-Length: %d\r\n\r\n".freeze
30
+ STATUS_STREAM = "HTTP/1.1 %d\r\nDate: %s\r\nConnection: close\r\nContent-Type: %s\r\n%s%s\r\n".freeze
31
+ STATUS_PERSIST = "HTTP/1.1 %d\r\nDate: %s\r\nContent-Type: %s\r\n%s%sContent-Length: %d\r\n\r\n".freeze
32
+ STATUS_REDIRECT = "HTTP/1.1 %d\r\nDate: %s\r\nConnection: close\r\nLocation: %s\r\n\r\n".freeze
33
+ HEADER = "%s: %s\r\n".freeze
34
+ EMPTY_STRING = ''.freeze
35
+ EMPTY_HASH = {}.freeze
36
+ SLASH = '/'.freeze
37
+ LOCATION = 'Location'.freeze
38
+ COOKIE = 'Cookie'
39
+ SET_COOKIE = "Set-Cookie: %s=%s; path=/; expires=%s\r\n".freeze
40
+ COOKIE_SPLIT = /[;,] */n.freeze
41
+ COOKIE_REGEXP = /\s*(.+)=(.*)\s*/.freeze
42
+ COOKIE_EXPIRED_TIME = Time.at(0).freeze
43
+ CONTENT_TYPE = "Content-Type".freeze
44
+ CONTENT_TYPE_URL_ENCODED = 'application/x-www-form-urlencoded'.freeze
45
+
46
46
  include StaticFiles
47
47
 
48
- attr_reader :conn, :method, :path, :query, :version, :parameters,
49
- :headers, :persistent, :cookies, :response_cookies
48
+ attr_reader :socket, :method, :path, :query, :version, :parameters,
49
+ :headers, :persistent, :cookies, :response_cookies, :body,
50
+ :content_length, :content_type
50
51
 
51
52
  # 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
53
+ # which override the initialize method must receive socket as the
54
+ # single argument, and copy it to @socket.
55
+ def initialize(socket)
56
+ @socket = socket
56
57
  end
57
58
 
58
59
  # Processes the request by parsing it and then responding.
@@ -65,29 +66,35 @@ module ServerSide
65
66
  # connection is persistent (by checking the HTTP version and the
66
67
  # 'Connection' header).
67
68
  def parse
68
- return nil unless @conn.gets =~ Const::RequestRegexp
69
+ return nil unless @socket.gets =~ REQUEST_REGEXP
69
70
  @method, @path, @query, @version = $1.downcase.to_sym, $2, $3, $4
70
71
  @parameters = @query ? parse_parameters(@query) : {}
71
72
  @headers = {}
72
- while (line = @conn.gets)
73
- break if line.nil? || (line == Const::LineBreak)
74
- if line =~ Const::HeaderRegexp
73
+ while (line = @socket.gets)
74
+ break if line.nil? || (line == LINE_BREAK)
75
+ if line =~ HEADER_REGEXP
75
76
  @headers[$1.freeze] = $2.freeze
76
77
  end
77
78
  end
78
- @persistent = (@version == Const::Version_1_1) &&
79
- (@headers[Const::Connection] != Const::Close)
80
- @cookies = @headers[Const::Cookie] ? parse_cookies : Const::EmptyHash
79
+ @persistent = (@version == VERSION_1_1) &&
80
+ (@headers[CONNECTION] != CLOSE)
81
+ @cookies = @headers[COOKIE] ? parse_cookies : EMPTY_HASH
81
82
  @response_cookies = nil
82
83
 
84
+ if @content_length = @headers[CONTENT_LENGTH].to_i
85
+ @content_type = @headers[CONTENT_TYPE] || CONTENT_TYPE_URL_ENCODED
86
+ @body = @socket.read(@content_length) rescue nil
87
+ parse_body
88
+ end
89
+
83
90
  @headers
84
91
  end
85
92
 
86
93
  # Parses query parameters by splitting the query string and unescaping
87
94
  # parameter values.
88
95
  def parse_parameters(query)
89
- query.split(Const::Ampersand).inject({}) do |m, i|
90
- if i =~ Const::ParameterRegexp
96
+ query.split(AMPERSAND).inject({}) do |m, i|
97
+ if i =~ PARAMETER_REGEXP
91
98
  m[$1.to_sym] = $2.uri_unescape
92
99
  end
93
100
  m
@@ -96,35 +103,68 @@ module ServerSide
96
103
 
97
104
  # Parses cookie values passed in the request
98
105
  def parse_cookies
99
- @headers[Const::Cookie].split(Const::CookieSplit).inject({}) do |m, i|
100
- if i =~ Const::CookieRegexp
106
+ @headers[COOKIE].split(COOKIE_SPLIT).inject({}) do |m, i|
107
+ if i =~ COOKIE_REGEXP
101
108
  m[$1.to_sym] = $2.uri_unescape
102
109
  end
103
110
  m
104
111
  end
105
112
  end
113
+
114
+ MULTIPART_REGEXP = /multipart\/form-data.*boundary=\"?([^\";,]+)/n.freeze
115
+ CONTENT_DISPOSITION_REGEXP = /^Content-Disposition: form-data;([^\r]*)/m.freeze
116
+ FIELD_ATTRIBUTE_REGEXP = /\s*(\w+)=\"([^\"]*)/.freeze
117
+ CONTENT_TYPE_REGEXP = /^Content-Type: ([^\r]*)/m.freeze
118
+
119
+ # parses the body, either by using
120
+ def parse_body
121
+ if @content_type == CONTENT_TYPE_URL_ENCODED
122
+ @parameters.merge! parse_parameters(@body)
123
+ elsif @content_type =~ MULTIPART_REGEXP
124
+ boundary = "--#$1"
125
+ @body.split(/(?:\r?\n|\A)#{Regexp::quote(boundary)}(?:--)?\r\n/m).each do |pt|
126
+ headers, payload = pt.split("\r\n\r\n", 2)
127
+ atts = {}
128
+ if headers =~ CONTENT_DISPOSITION_REGEXP
129
+ $1.split(';').map {|part|
130
+ if part =~ FIELD_ATTRIBUTE_REGEXP
131
+ atts[$1.to_sym] = $2
132
+ end
133
+ }
134
+ end
135
+ if headers =~ CONTENT_TYPE_REGEXP
136
+ atts[:type] = $1
137
+ end
138
+ if name = atts[:name]
139
+ atts[:content] = payload
140
+ @parameters[name.to_sym] = atts[:filename] ? atts : atts[:content]
141
+ end
142
+ end
143
+ end
144
+ end
106
145
 
107
146
  # Sends an HTTP response.
108
147
  def send_response(status, content_type, body = nil, content_length = nil,
109
148
  headers = nil)
110
149
  h = headers ?
111
- headers.inject('') {|m, kv| m << (Const::Header % kv)} : ''
150
+ headers.inject('') {|m, kv| m << (HEADER % kv)} : ''
112
151
 
113
152
  content_length = body.length if content_length.nil? && body
114
153
  @persistent = false if content_length.nil?
115
154
 
116
155
  # 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
156
+ @socket << ((@persistent ? STATUS_PERSIST :
157
+ (body ? STATUS_CLOSE : STATUS_STREAM)) %
158
+ [status, Time.now.httpdate, content_type, h, @response_cookies, content_length])
159
+ @socket << body if body
121
160
  rescue
122
161
  @persistent = false
123
162
  end
124
163
 
125
164
  # Send a redirect response.
126
165
  def redirect(location, permanent = false)
127
- @conn << (Const::StatusRedirect % [permanent ? 301 : 302, location])
166
+ @socket << (STATUS_REDIRECT %
167
+ [permanent ? 301 : 302, Time.now.httpdate, location])
128
168
  rescue
129
169
  ensure
130
170
  @persistent = false
@@ -132,18 +172,18 @@ module ServerSide
132
172
 
133
173
  # Streams additional data to the client.
134
174
  def stream(body)
135
- (@conn << body if body) rescue (@persistent = false)
175
+ (@socket << body if body) rescue (@persistent = false)
136
176
  end
137
177
 
138
178
  # Sets a cookie to be included in the response.
139
179
  def set_cookie(name, value, expires)
140
180
  @response_cookies ||= ""
141
- @response_cookies << (Const::SetCookie % [name, value.to_s.uri_escape, expires.rfc2822])
181
+ @response_cookies << (SET_COOKIE % [name, value.to_s.uri_escape, expires.rfc2822])
142
182
  end
143
183
 
144
184
  # Marks a cookie as deleted. The cookie is given an expires stamp in the past.
145
185
  def delete_cookie(name)
146
- set_cookie(name, nil, Const::CookieExpiredTime)
186
+ set_cookie(name, nil, COOKIE_EXPIRED_TIME)
147
187
  end
148
188
  end
149
189
  end