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