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