serverside 0.2.6 → 0.2.7
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.
- data/CHANGELOG +22 -16
- data/Rakefile +11 -4
- data/bin/serverside +1 -1
- data/doc/rdoc/classes/Daemon.html +4 -4
- data/doc/rdoc/classes/ServerSide.html +10 -10
- data/doc/rdoc/classes/ServerSide/HTTP.html +5 -6
- data/doc/rdoc/classes/ServerSide/HTTP/Connection.html +24 -22
- data/doc/rdoc/classes/ServerSide/HTTP/Request.html +367 -121
- data/doc/rdoc/classes/ServerSide/Router.html +58 -58
- data/doc/rdoc/classes/ServerSide/StaticFiles.html +60 -49
- data/doc/rdoc/classes/ServerSide/StaticFiles/Const.html +12 -2
- data/doc/rdoc/classes/ServerSide/Template.html +12 -12
- data/doc/rdoc/created.rid +1 -1
- data/doc/rdoc/files/CHANGELOG.html +36 -31
- data/doc/rdoc/files/COPYING.html +1 -1
- data/doc/rdoc/files/README.html +1 -1
- data/doc/rdoc/files/lib/serverside/application_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/cluster_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/connection_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/core_ext_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/daemon_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/request_rb.html +8 -1
- data/doc/rdoc/files/lib/serverside/routing_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/server_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside/static_rb.html +8 -1
- data/doc/rdoc/files/lib/serverside/template_rb.html +1 -1
- data/doc/rdoc/files/lib/serverside_rb.html +1 -1
- data/doc/rdoc/fr_class_index.html +0 -1
- data/doc/rdoc/fr_method_index.html +21 -20
- data/lib/serverside/connection.rb +6 -4
- data/lib/serverside/request.rb +101 -61
- data/lib/serverside/static.rb +22 -7
- data/test/functional/request_body_test.rb +93 -0
- data/test/functional/routing_server.rb +5 -5
- data/test/functional/routing_server_test.rb +3 -3
- data/test/spec/connection_spec.rb +61 -0
- data/test/spec/core_ext_spec.rb +20 -1
- data/test/unit/connection_test.rb +9 -9
- data/test/unit/request_test.rb +55 -55
- data/test/unit/server_test.rb +1 -1
- data/test/unit/static_test.rb +24 -25
- metadata +60 -60
- data/doc/rdoc/classes/ServerSide/HTTP/Const.html +0 -257
data/doc/rdoc/created.rid
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
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>
|
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
|
-
<
|
73
|
-
<
|
74
|
-
|
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
|
-
<
|
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
|
-
<
|
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’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
|
-
<
|
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
|
|
data/doc/rdoc/files/COPYING.html
CHANGED
data/doc/rdoc/files/README.html
CHANGED
@@ -56,7 +56,7 @@
|
|
56
56
|
</tr>
|
57
57
|
<tr class="top-aligned-row">
|
58
58
|
<td><strong>Last Update:</strong></td>
|
59
|
-
<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
|
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>
|
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
|
77
|
+
</div>
|
78
|
+
</div>
|
72
79
|
|
73
80
|
</div>
|
74
81
|
|
@@ -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#
|
25
|
-
<a href="classes/ServerSide/Router.html#
|
26
|
-
<a href="classes/ServerSide/Router.html#
|
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#
|
33
|
-
<a href="classes/ServerSide/Router.html#
|
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#
|
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#
|
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#
|
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#
|
51
|
-
<a href="classes/ServerSide/Template.html#
|
52
|
-
<a href="classes/ServerSide/Router.html#
|
53
|
-
<a href="classes/ServerSide/Router.html#
|
54
|
-
<a href="classes/ServerSide/Router.html#
|
55
|
-
<a href="classes/ServerSide/HTTP/Request.html#
|
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#
|
61
|
-
<a href="classes/ServerSide/HTTP/Request.html#
|
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#
|
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#
|
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(
|
14
|
-
@
|
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
|
-
|
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
|
-
@
|
30
|
+
@socket.close
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
data/lib/serverside/request.rb
CHANGED
@@ -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
|
-
#
|
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 :
|
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
|
53
|
-
# single argument, and copy it to @
|
54
|
-
def initialize(
|
55
|
-
@
|
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 @
|
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 = @
|
73
|
-
break if line.nil? || (line ==
|
74
|
-
if line =~
|
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 ==
|
79
|
-
(@headers[
|
80
|
-
@cookies = @headers[
|
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(
|
90
|
-
if i =~
|
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[
|
100
|
-
if i =~
|
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 << (
|
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
|
-
@
|
118
|
-
(body ?
|
119
|
-
[status, content_type, h, @response_cookies, content_length])
|
120
|
-
@
|
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
|
-
@
|
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
|
-
(@
|
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 << (
|
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,
|
186
|
+
set_cookie(name, nil, COOKIE_EXPIRED_TIME)
|
147
187
|
end
|
148
188
|
end
|
149
189
|
end
|