serverside 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +10 -0
- data/README +3 -3
- data/Rakefile +4 -4
- data/bin/serverside +4 -4
- data/doc/rdoc/classes/Daemon.html +4 -4
- data/doc/rdoc/classes/ServerSide.html +14 -74
- data/doc/rdoc/classes/ServerSide/Application.html +13 -13
- data/doc/rdoc/classes/ServerSide/{Connection.html → HTTP.html} +12 -18
- data/doc/rdoc/classes/ServerSide/HTTP/Connection.html +200 -0
- data/doc/rdoc/classes/ServerSide/{Connection → HTTP}/Const.html +6 -6
- data/doc/rdoc/classes/ServerSide/{Connection/Base.html → HTTP/Request.html} +213 -164
- data/doc/rdoc/classes/ServerSide/{Server.html → HTTP/Server.html} +22 -22
- data/doc/rdoc/classes/ServerSide/Router.html +496 -0
- data/doc/rdoc/classes/ServerSide/StaticFiles.html +31 -30
- data/doc/rdoc/classes/ServerSide/Template.html +12 -12
- data/doc/rdoc/created.rid +1 -1
- data/doc/rdoc/files/CHANGELOG.html +22 -4
- data/doc/rdoc/files/COPYING.html +1 -1
- data/doc/rdoc/files/README.html +4 -4
- 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 +101 -0
- 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 +1 -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 +6 -5
- data/doc/rdoc/fr_file_index.html +1 -0
- data/doc/rdoc/fr_method_index.html +31 -31
- data/lib/serverside/application.rb +1 -1
- data/lib/serverside/connection.rb +12 -141
- data/lib/serverside/request.rb +150 -0
- data/lib/serverside/routing.rb +117 -126
- data/lib/serverside/server.rb +13 -12
- data/lib/serverside/static.rb +1 -1
- data/test/functional/primitive_static_server_test.rb +3 -3
- data/test/functional/routing_server.rb +1 -1
- data/test/functional/routing_server_test.rb +1 -1
- data/test/functional/static_profile.rb +1 -1
- data/test/functional/static_server_test.rb +2 -2
- data/test/unit/connection_test.rb +30 -224
- data/test/unit/request_test.rb +248 -0
- data/test/unit/routing_test.rb +4 -4
- data/test/unit/server_test.rb +10 -8
- data/test/unit/static_test.rb +1 -1
- metadata +61 -56
- data/doc/rdoc/classes/ServerSide/Connection/Router.html +0 -493
@@ -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/
|
33
|
-
<a href="classes/ServerSide/Connection
|
34
|
-
<a href="classes/ServerSide/
|
35
|
-
<a href="classes/ServerSide/
|
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 />
|
data/doc/rdoc/fr_file_index.html
CHANGED
@@ -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/
|
25
|
-
<a href="classes/ServerSide/
|
26
|
-
<a href="classes/ServerSide/
|
27
|
-
<a href="classes/ServerSide/Application.html#
|
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#
|
32
|
-
<a href="classes/ServerSide/
|
33
|
-
<a href="classes/ServerSide/
|
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/
|
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/
|
38
|
-
<a href="classes/ServerSide/
|
39
|
-
<a href="classes/ServerSide/
|
40
|
-
<a href="classes/ServerSide/Connection
|
41
|
-
<a href="classes/ServerSide/
|
42
|
-
<a href="classes/ServerSide/
|
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/
|
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/
|
49
|
-
<a href="classes/ServerSide/Template.html#
|
50
|
-
<a href="classes/ServerSide.html#
|
51
|
-
<a href="classes/ServerSide/
|
52
|
-
<a href="classes/ServerSide.html#
|
53
|
-
<a href="classes/ServerSide/
|
54
|
-
<a href="classes/ServerSide/
|
55
|
-
<a href="classes/ServerSide/
|
56
|
-
<a href="classes/ServerSide/StaticFiles.html#M000025">
|
57
|
-
<a href="classes/ServerSide/StaticFiles.html#M000024">
|
58
|
-
<a href="classes/ServerSide/
|
59
|
-
<a href="classes/ServerSide/
|
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/
|
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/
|
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::
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
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
|
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
|