serverside 0.3.1 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +15 -11
- data/Rakefile +18 -18
- data/bin/serverside +20 -16
- data/lib/serverside/cluster.rb +4 -33
- data/lib/serverside/core_ext.rb +56 -7
- data/lib/serverside/daemon.rb +10 -17
- data/lib/serverside/http/caching.rb +79 -0
- data/lib/serverside/http/const.rb +69 -0
- data/lib/serverside/http/error.rb +24 -0
- data/lib/serverside/http/parsing.rb +175 -0
- data/lib/serverside/http/response.rb +91 -0
- data/lib/serverside/http/server.rb +194 -0
- data/lib/serverside/http/static.rb +72 -0
- data/lib/serverside/http.rb +14 -0
- data/lib/serverside/js.rb +173 -0
- data/lib/serverside/log.rb +79 -0
- data/lib/serverside/template.rb +5 -4
- data/lib/serverside/xml.rb +84 -0
- data/lib/serverside.rb +11 -2
- data/spec/core_ext_spec.rb +13 -58
- data/spec/daemon_spec.rb +61 -28
- data/spec/http_spec.rb +259 -0
- data/spec/template_spec.rb +9 -7
- metadata +42 -28
- data/CHANGELOG +0 -261
- data/lib/serverside/application.rb +0 -26
- data/lib/serverside/caching.rb +0 -91
- data/lib/serverside/connection.rb +0 -34
- data/lib/serverside/controllers.rb +0 -91
- data/lib/serverside/request.rb +0 -210
- data/lib/serverside/routing.rb +0 -133
- data/lib/serverside/server.rb +0 -27
- data/lib/serverside/static.rb +0 -82
- data/spec/caching_spec.rb +0 -318
- data/spec/cluster_spec.rb +0 -140
- data/spec/connection_spec.rb +0 -59
- data/spec/controllers_spec.rb +0 -142
- data/spec/request_spec.rb +0 -288
- data/spec/routing_spec.rb +0 -240
- data/spec/server_spec.rb +0 -40
- data/spec/static_spec.rb +0 -279
data/README
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
|
1
|
+
== ServerSide: A Fast HTTP Server for Ruby
|
2
2
|
|
3
|
-
ServerSide is an HTTP server
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
ServerSide is an HTTP server based on EventMachine, a fast event processing
|
4
|
+
library that offers superior scalability. ServerSide offers features such as
|
5
|
+
persistent connections, HTTP streaming, HTTP caching, and more. ServerSide
|
6
|
+
also includes tools to easily serve static files and create clusters.
|
7
7
|
|
8
8
|
== Resources
|
9
9
|
|
@@ -16,6 +16,10 @@ To check out the source code:
|
|
16
16
|
|
17
17
|
svn co http://serverside.googlecode.com/svn/trunk
|
18
18
|
|
19
|
+
=== Contact
|
20
|
+
|
21
|
+
If you have any comments or suggestions please send an email to ciconia at gmail.com and I'll get back to you.
|
22
|
+
|
19
23
|
== Installation
|
20
24
|
|
21
25
|
sudo gem install serverside
|
@@ -34,7 +38,7 @@ To run the server without forking, use the 'serve' command:
|
|
34
38
|
|
35
39
|
serverside serve .
|
36
40
|
|
37
|
-
|
41
|
+
=== Serving ERb Templates
|
38
42
|
|
39
43
|
ServerSide can render ERb[http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/]
|
40
44
|
templates in a fashion similar to PHP. You can store templates in .rhtml files,
|
@@ -42,15 +46,15 @@ and ServerSide takes care of all the rest. ServerSide is also smart enough to
|
|
42
46
|
allow you to use nice looking URL's with your templates, and automatically adds
|
43
47
|
the .rhtml extension if the file is there.
|
44
48
|
|
45
|
-
|
49
|
+
=== Serving Dynamic Content
|
46
50
|
|
47
51
|
By default ServerSide serves static files, but you can change the behavior by
|
48
52
|
creating custom {routing rules}[classes/ServerSide/Connection/Router.html].
|
49
53
|
Here's a simple routing rule:
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
55
|
+
route :path => '/hello/:name' do
|
56
|
+
send_response(200, 'text', "Hello #{@params[:name]}")
|
57
|
+
end
|
54
58
|
|
55
59
|
The ServerSide framework also lets you route requests based on any attribute of
|
56
60
|
incoming requests, such as host name, path, URL parameters etc.
|
@@ -60,7 +64,7 @@ or tell serverside to explicitly load a specific file:
|
|
60
64
|
|
61
65
|
serverside start ~/myapp/myapp.rb
|
62
66
|
|
63
|
-
|
67
|
+
=== Running a Cluster of Servers
|
64
68
|
|
65
69
|
ServerSide makes it easy to control a cluster of servers. Just supply a range of
|
66
70
|
ports instead of a single port:
|
data/Rakefile
CHANGED
@@ -6,9 +6,9 @@ require 'fileutils'
|
|
6
6
|
include FileUtils
|
7
7
|
|
8
8
|
NAME = "serverside"
|
9
|
-
VERS = "0.
|
9
|
+
VERS = "0.4.1"
|
10
10
|
CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
|
11
|
-
RDOC_OPTS = ['--quiet', '--title', "ServerSide
|
11
|
+
RDOC_OPTS = ['--quiet', '--title', "ServerSide: a Fast Ruby Web Framework",
|
12
12
|
"--opname", "index.html",
|
13
13
|
"--line-numbers",
|
14
14
|
"--main", "README",
|
@@ -24,8 +24,8 @@ Rake::RDocTask.new do |rdoc|
|
|
24
24
|
rdoc.rdoc_dir = 'doc/rdoc'
|
25
25
|
rdoc.options += RDOC_OPTS
|
26
26
|
rdoc.main = "README"
|
27
|
-
rdoc.title = "ServerSide
|
28
|
-
rdoc.rdoc_files.add ['README', '
|
27
|
+
rdoc.title = "ServerSide: a Fast Ruby HTTP Server"
|
28
|
+
rdoc.rdoc_files.add ['README', 'COPYING', 'lib/serverside.rb', 'lib/serverside/**/*.rb']
|
29
29
|
end
|
30
30
|
|
31
31
|
spec = Gem::Specification.new do |s|
|
@@ -33,20 +33,22 @@ spec = Gem::Specification.new do |s|
|
|
33
33
|
s.version = VERS
|
34
34
|
s.platform = Gem::Platform::RUBY
|
35
35
|
s.has_rdoc = true
|
36
|
-
s.extra_rdoc_files = ["README", "
|
36
|
+
s.extra_rdoc_files = ["README", "COPYING"]
|
37
37
|
s.rdoc_options += RDOC_OPTS +
|
38
38
|
['--exclude', '^(examples|extras)\/', '--exclude', 'lib/serverside.rb']
|
39
|
-
s.summary = "
|
39
|
+
s.summary = "Fast Ruby HTTP Server."
|
40
40
|
s.description = s.summary
|
41
41
|
s.author = "Sharon Rosner"
|
42
42
|
s.email = 'ciconia@gmail.com'
|
43
43
|
s.homepage = 'http://code.google.com/p/serverside/'
|
44
44
|
s.executables = ['serverside']
|
45
45
|
|
46
|
+
s.add_dependency('eventmachine')
|
47
|
+
s.add_dependency('erubis')
|
46
48
|
s.add_dependency('metaid')
|
47
|
-
s.required_ruby_version = '>= 1.8.
|
49
|
+
s.required_ruby_version = '>= 1.8.5'
|
48
50
|
|
49
|
-
s.files = %w(COPYING README Rakefile) + Dir.glob("{bin,doc,spec,lib}/**/*")
|
51
|
+
s.files = %w(COPYING README Rakefile) + Dir.glob("{bin,doc,spec,lib}/**/*")
|
50
52
|
|
51
53
|
s.require_path = "lib"
|
52
54
|
s.bindir = "bin"
|
@@ -74,26 +76,24 @@ end
|
|
74
76
|
|
75
77
|
require 'spec/rake/spectask'
|
76
78
|
|
77
|
-
desc "Run specs
|
79
|
+
desc "Run specs"
|
78
80
|
Spec::Rake::SpecTask.new('spec') do |t|
|
79
81
|
t.spec_files = FileList['spec/*_spec.rb']
|
80
|
-
t.rcov = true
|
81
82
|
end
|
82
83
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
#end
|
84
|
+
desc "Run specs with coverage"
|
85
|
+
Spec::Rake::SpecTask.new('spec_coverage') do |t|
|
86
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
87
|
+
t.rcov = true
|
88
|
+
end
|
89
89
|
|
90
90
|
##############################################################################
|
91
91
|
# Statistics
|
92
92
|
##############################################################################
|
93
93
|
|
94
94
|
STATS_DIRECTORIES = [
|
95
|
-
%w(Code
|
96
|
-
%w(
|
95
|
+
%w(Code lib/),
|
96
|
+
%w(Spec spec/)
|
97
97
|
].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
|
98
98
|
|
99
99
|
desc "Report code statistics (KLOCs, etc) from the application"
|
data/bin/serverside
CHANGED
@@ -10,10 +10,10 @@ $cmd_config = {
|
|
10
10
|
}
|
11
11
|
|
12
12
|
opts = OptionParser.new do |opts|
|
13
|
-
opts.banner = "Usage: serverside start|stop|restart|serve [
|
13
|
+
opts.banner = "Usage: serverside start|stop|restart|serve [app_file]"
|
14
14
|
opts.define_head "ServerSide, a fast and simple web framework for ruby."
|
15
15
|
opts.separator ""
|
16
|
-
opts.separator "The supplied
|
16
|
+
opts.separator "The supplied application path can be directory or file references. \
|
17
17
|
If the path refers to a directory the system will try to load serverside.rb in \
|
18
18
|
that directory. If no path is given, the current working directory is assumed."
|
19
19
|
opts.separator ""
|
@@ -57,15 +57,22 @@ unless %w(start stop restart serve).include?($cmd)
|
|
57
57
|
exit
|
58
58
|
end
|
59
59
|
|
60
|
-
path = ARGV.shift || '.'
|
61
|
-
if File.file?(path)
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
require fn if File.file?(fn)
|
60
|
+
$path = ARGV.shift || '.'
|
61
|
+
if File.file?($path)
|
62
|
+
app_code = IO.read($path)
|
63
|
+
elsif File.file?($path/'serverside.rb')
|
64
|
+
app_code = IO.read($path/'serverside.rb')
|
66
65
|
end
|
67
|
-
|
68
|
-
|
66
|
+
|
67
|
+
$server = ServerSide::HTTP::Server.new do
|
68
|
+
if app_code
|
69
|
+
module_eval(app_code)
|
70
|
+
else
|
71
|
+
include ServerSide::HTTP::Static
|
72
|
+
def handle
|
73
|
+
serve_static($path/@uri)
|
74
|
+
end
|
75
|
+
end
|
69
76
|
end
|
70
77
|
|
71
78
|
if $cmd == 'serve'
|
@@ -75,18 +82,15 @@ if $cmd == 'serve'
|
|
75
82
|
end
|
76
83
|
puts "Serving at #{$cmd_config[:host]}:#{$cmd_config[:ports].begin}..."
|
77
84
|
trap('INT') {exit}
|
78
|
-
|
79
|
-
|
85
|
+
|
86
|
+
$server.start($cmd_config[:host], $cmd_config[:ports].begin)
|
80
87
|
else
|
81
88
|
daemon_class = Class.new(Daemon::Cluster) do
|
82
89
|
meta_def(:pid_fn) {Daemon::WorkingDirectory/'serverside.pid'}
|
83
90
|
meta_def(:server_loop) do |port|
|
84
|
-
|
85
|
-
$cmd_config[:host], port, ServerSide::Router).start
|
91
|
+
$server.start($cmd_config[:host], port)
|
86
92
|
end
|
87
93
|
meta_def(:ports) {$cmd_config[:ports]}
|
88
94
|
end
|
89
95
|
Daemon.control(daemon_class, $cmd)
|
90
|
-
|
91
|
-
# ServerSide::Application.daemonize($cmd_config, $cmd)
|
92
96
|
end
|
data/lib/serverside/cluster.rb
CHANGED
@@ -4,32 +4,6 @@ module Daemon
|
|
4
4
|
# Implements a cluster controlling daemon. The daemon itself itself forks
|
5
5
|
# a child process for each port.
|
6
6
|
class Cluster < Base
|
7
|
-
# Stores and recalls pids for the child processes.
|
8
|
-
class PidFile
|
9
|
-
FN = 'serverside_cluster.pid'
|
10
|
-
|
11
|
-
# Deletes the cluster's pid file.
|
12
|
-
def self.delete
|
13
|
-
FileUtils.rm(FN) if File.file?(FN)
|
14
|
-
end
|
15
|
-
|
16
|
-
# Stores a pid in the cluster's pid file.
|
17
|
-
def self.store_pid(pid)
|
18
|
-
File.open(FN, 'a') {|f| f.puts pid}
|
19
|
-
end
|
20
|
-
|
21
|
-
# Recalls all child pids.
|
22
|
-
def self.recall_pids
|
23
|
-
pids = []
|
24
|
-
File.open(FN, 'r') do |f|
|
25
|
-
while !f.eof?
|
26
|
-
pids << f.gets.to_i
|
27
|
-
end
|
28
|
-
end
|
29
|
-
pids
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
7
|
# Forks a child process with a specific port.
|
34
8
|
def self.fork_server(port)
|
35
9
|
fork do
|
@@ -37,20 +11,17 @@ module Daemon
|
|
37
11
|
server_loop(port)
|
38
12
|
end
|
39
13
|
end
|
14
|
+
|
15
|
+
@@pids = []
|
40
16
|
|
41
17
|
# Starts child processes.
|
42
18
|
def self.start_servers
|
43
|
-
|
44
|
-
ports.each do |p|
|
45
|
-
PidFile.store_pid(fork_server(p))
|
46
|
-
end
|
19
|
+
ports.each {|p| @@pids << fork_server(p)}
|
47
20
|
end
|
48
21
|
|
49
22
|
# Stops child processes.
|
50
23
|
def self.stop_servers
|
51
|
-
pids
|
52
|
-
pids.each {|pid| begin; Process.kill('TERM', pid); rescue; end}
|
53
|
-
PidFile.delete
|
24
|
+
@@pids.each {|pid| Process.kill('TERM', pid) rescue nil}
|
54
25
|
end
|
55
26
|
|
56
27
|
# The main daemon loop. Does nothing for now.
|
data/lib/serverside/core_ext.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
require 'md5'
|
2
|
+
|
3
|
+
# String extensions.
|
2
4
|
class String
|
3
5
|
# Encodes a normal string to a URI string.
|
4
6
|
def uri_escape
|
@@ -11,7 +13,11 @@ class String
|
|
11
13
|
tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){[$1.delete('%')].pack('H*')}
|
12
14
|
end
|
13
15
|
|
14
|
-
|
16
|
+
def html_escape
|
17
|
+
gsub(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/</, "<")
|
18
|
+
end
|
19
|
+
|
20
|
+
# Concatenates a path (purely sugar)
|
15
21
|
def /(o)
|
16
22
|
File.join(self, o.to_s)
|
17
23
|
end
|
@@ -21,14 +27,45 @@ class String
|
|
21
27
|
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
22
28
|
tr("-", "_").downcase
|
23
29
|
end
|
30
|
+
|
31
|
+
# Converts an underscored name into a camelized name
|
32
|
+
def camelize
|
33
|
+
gsub(/(^|_)(.)/) {$2.upcase}
|
34
|
+
end
|
35
|
+
|
36
|
+
LINE_RE = /^([^\r]*)\r\n/n.freeze
|
37
|
+
EMPTY_STRING = ''.freeze
|
38
|
+
|
39
|
+
def get_line
|
40
|
+
sub!(LINE_RE, EMPTY_STRING) ? $1 : nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_up_to_boundary(boundary)
|
44
|
+
if i = index(boundary)
|
45
|
+
part = i > 0 ? self[0..(i - 1)] : ''
|
46
|
+
slice!(0..(i + boundary.size - 1))
|
47
|
+
part
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_up_to_boundary_with_crlf(boundary)
|
52
|
+
if i = index(boundary)
|
53
|
+
part = i > 0 ? self[0..(i - 1)] : ''
|
54
|
+
slice!(0..(i + boundary.size + 1))
|
55
|
+
part
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def etag
|
60
|
+
MD5.hexdigest(self)
|
61
|
+
end
|
24
62
|
end
|
25
63
|
|
26
|
-
# Symbol extensions
|
64
|
+
# Symbol extensions.
|
27
65
|
class Symbol
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
@_to_s || (@_to_s = id2name)
|
66
|
+
# Concatenates a path (purely sugar)
|
67
|
+
def /(o)
|
68
|
+
File.join(self, o.to_s)
|
32
69
|
end
|
33
70
|
end
|
34
71
|
|
@@ -46,3 +83,15 @@ class Object
|
|
46
83
|
end
|
47
84
|
end
|
48
85
|
|
86
|
+
# Coercion of boolean values to integer
|
87
|
+
def true.to_i; -1; end
|
88
|
+
def false.to_i; 0; end
|
89
|
+
|
90
|
+
# Process extensions.
|
91
|
+
module Process
|
92
|
+
# Checks for the existance of a process.
|
93
|
+
def self.exists?(pid)
|
94
|
+
getpgid(pid) && true rescue false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
data/lib/serverside/daemon.rb
CHANGED
@@ -1,15 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
|
3
|
-
class Object
|
4
|
-
def backtrace
|
5
|
-
raise RuntimeError
|
6
|
-
rescue => e
|
7
|
-
bt = e.backtrace.dup
|
8
|
-
bt.shift
|
9
|
-
bt
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
3
|
# The Daemon module takes care of starting and stopping daemons.
|
14
4
|
module Daemon
|
15
5
|
WorkingDirectory = FileUtils.pwd
|
@@ -49,7 +39,11 @@ module Daemon
|
|
49
39
|
when :stop
|
50
40
|
stop(daemon)
|
51
41
|
when :restart
|
52
|
-
|
42
|
+
begin
|
43
|
+
stop(daemon)
|
44
|
+
sleep 2
|
45
|
+
rescue
|
46
|
+
end
|
53
47
|
start(daemon)
|
54
48
|
else
|
55
49
|
raise 'Invalid command. Please specify start, stop or restart.'
|
@@ -64,9 +58,9 @@ module Daemon
|
|
64
58
|
PidFile.store(daemon, Process.pid)
|
65
59
|
Dir.chdir WorkingDirectory
|
66
60
|
File.umask 0000
|
67
|
-
|
68
|
-
|
69
|
-
|
61
|
+
STDIN.reopen "/dev/null"
|
62
|
+
STDOUT.reopen "/dev/null", "a"
|
63
|
+
STDERR.reopen STDOUT
|
70
64
|
trap("TERM") {daemon.stop; exit}
|
71
65
|
daemon.start
|
72
66
|
end
|
@@ -75,13 +69,12 @@ module Daemon
|
|
75
69
|
# Stops the daemon by sending it a TERM signal.
|
76
70
|
def self.stop(daemon)
|
77
71
|
pid = PidFile.recall(daemon)
|
78
|
-
pid && Process.kill("TERM", pid)
|
72
|
+
pid && Process.kill("TERM", pid) rescue nil
|
79
73
|
PidFile.remove(daemon)
|
80
74
|
end
|
81
75
|
|
82
76
|
def self.alive?(daemon)
|
83
77
|
pid = PidFile.recall(daemon) rescue nil
|
84
|
-
|
85
|
-
`ps #{pid}` =~ /#{pid}/ ? true : nil
|
78
|
+
pid ? Process.exists?(pid) : false
|
86
79
|
end
|
87
80
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module ServerSide::HTTP
|
2
|
+
# HTTP Caching behavior
|
3
|
+
module Caching
|
4
|
+
# Sets caching-related headers and validates If-Modified-Since and
|
5
|
+
# If-None-Match headers. If a match is found, a 304 response is sent.
|
6
|
+
# Otherwise, the supplied block is invoked.
|
7
|
+
def cache(opts)
|
8
|
+
not_modified = false
|
9
|
+
|
10
|
+
# check etag
|
11
|
+
if etag = opts[:etag]
|
12
|
+
etag = "\"#{etag}\""
|
13
|
+
add_header(ETAG, etag) if etag
|
14
|
+
not_modified = etag_match(etag)
|
15
|
+
end
|
16
|
+
|
17
|
+
# check last_modified
|
18
|
+
if last_modified = opts[:last_modified]
|
19
|
+
add_header(LAST_MODIFIED, last_modified)
|
20
|
+
not_modified ||= modified_match(last_modified)
|
21
|
+
end
|
22
|
+
|
23
|
+
# check cache-control
|
24
|
+
remove_cache_control
|
25
|
+
if cache_control = opts[:cache_control]
|
26
|
+
add_header(CACHE_CONTROL, cache_control)
|
27
|
+
end
|
28
|
+
|
29
|
+
# add an expires header
|
30
|
+
if expires = opts[:expires]
|
31
|
+
add_header(EXPIRES, expires.httpdate)
|
32
|
+
elsif age = opts[:age]
|
33
|
+
add_header(EXPIRES, (Time.now + age).httpdate)
|
34
|
+
end
|
35
|
+
|
36
|
+
# if not modified, send a 304 response. Otherwise we yield to the
|
37
|
+
# supplied block.
|
38
|
+
not_modified ?
|
39
|
+
send_response(STATUS_NOT_MODIFIED, nil) : yield
|
40
|
+
end
|
41
|
+
|
42
|
+
COMMA = ','.freeze
|
43
|
+
|
44
|
+
# Matches the supplied etag against any of the entities in the
|
45
|
+
# If-None-Match header.
|
46
|
+
def etag_match(etag)
|
47
|
+
matches = @request_headers[IF_NONE_MATCH]
|
48
|
+
if matches
|
49
|
+
matches.split(COMMA).each do |e|
|
50
|
+
|
51
|
+
return true if e.strip == etag
|
52
|
+
end
|
53
|
+
end
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
# Matches the supplied last modified date against the If-Modified-Since
|
58
|
+
# header.
|
59
|
+
def modified_match(last_modified)
|
60
|
+
if modified_since = @request_headers[IF_MODIFIED_SINCE]
|
61
|
+
last_modified.to_i == Time.parse(modified_since).to_i
|
62
|
+
else
|
63
|
+
false
|
64
|
+
end
|
65
|
+
rescue => e
|
66
|
+
raise MalformedRequestError, "Invalid value in If-Modified-Since header"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Sets the Cache-Control header.
|
70
|
+
def set_cache_control(directive)
|
71
|
+
add_header(CACHE_CONTROL, directive)
|
72
|
+
end
|
73
|
+
|
74
|
+
def remove_cache_control
|
75
|
+
@response_headers.reject! {|h| h =~ /^#{CACHE_CONTROL}/}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ServerSide::HTTP
|
2
|
+
# HTTP versions
|
3
|
+
VERSION_1_0 = '1.0'.freeze
|
4
|
+
VERSION_1_1 = '1.1'.freeze
|
5
|
+
|
6
|
+
# maximum sizes
|
7
|
+
MAX_REQUEST_LINE_SIZE = 8192
|
8
|
+
MAX_HEADER_SIZE = 8192
|
9
|
+
MAX_HEADER_COUNT = 256 # should be enough methinks
|
10
|
+
|
11
|
+
# request body and response body
|
12
|
+
CONTENT_LENGTH = 'Content-Length'.freeze
|
13
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
14
|
+
MULTIPART_FORM_DATA_RE = /^multipart\/form-data; boundary=(.+)$/.freeze
|
15
|
+
CONTENT_DISPOSITION = 'Content-Disposition'.freeze
|
16
|
+
DISPOSITION_FORM_DATA_RE = /^form-data; name="([^"]+)"(; filename="([^"]+)")?$/.freeze
|
17
|
+
FORM_URL_ENCODED = 'application/x-www-form-urlencoded'.freeze
|
18
|
+
|
19
|
+
# connection
|
20
|
+
CONNECTION = 'Connection'.freeze
|
21
|
+
KEEP_ALIVE = 'keep-alive'.freeze
|
22
|
+
CLOSE = 'close'.freeze
|
23
|
+
CONNECTION_CLOSE = "Connection: close\r\n".freeze
|
24
|
+
|
25
|
+
# headers
|
26
|
+
HOST = 'Host'.freeze
|
27
|
+
X_FORWARDED_FOR = 'X-Forwarded-For'.freeze
|
28
|
+
DATE = 'Date'.freeze
|
29
|
+
LOCATION = 'Location'.freeze
|
30
|
+
|
31
|
+
# caching
|
32
|
+
IF_NONE_MATCH = 'If-None-Match'.freeze
|
33
|
+
IF_MODIFIED_SINCE = 'If-Modified-Since'.freeze
|
34
|
+
ETAG = 'ETag'.freeze
|
35
|
+
LAST_MODIFIED = 'Last-Modified'.freeze
|
36
|
+
CACHE_CONTROL = 'Cache-Control'.freeze
|
37
|
+
NO_CACHE = 'no-cache'.freeze
|
38
|
+
EXPIRES = 'Expires'.freeze
|
39
|
+
|
40
|
+
# response status
|
41
|
+
STATUS_OK = '200 OK'.freeze
|
42
|
+
STATUS_CREATED = '201 Created'.freeze
|
43
|
+
STATUS_ACCEPTED = '202 Accepted'.freeze
|
44
|
+
STATUS_NO_CONTENT = '204 No Content'.freeze
|
45
|
+
|
46
|
+
STATUS_MOVED_PERMANENTLY = '301 Moved Permanently'.freeze
|
47
|
+
STATUS_FOUND = '302 Found'.freeze
|
48
|
+
STATUS_NOT_MODIFIED = '304 Not Modified'.freeze
|
49
|
+
|
50
|
+
STATUS_BAD_REQUEST = '400 Bad Request'.freeze
|
51
|
+
STATUS_UNAUTHORIZED = '401 Unauthorized'.freeze
|
52
|
+
STATUS_FORBIDDEN = '403 Forbidden'.freeze
|
53
|
+
STATUS_NOT_FOUND = '404 Not Found'.freeze
|
54
|
+
STATUS_METHOD_NOT_ALLOWED = '405 Method Not Allowed'.freeze
|
55
|
+
STATUS_NOT_ACCEPTABLE = '406 Not Acceptable'.freeze
|
56
|
+
STATUS_CONFLICT = '409 Conflict'.freeze
|
57
|
+
STATUS_REQUEST_ENTITY_TOO_LARGE = '413 Request Entity Too Large'.freeze
|
58
|
+
STATUS_REQUEST_URI_TOO_LONG = '414 Request-URI Too Long'.freeze
|
59
|
+
STATUS_UNSUPPORTED_MEDIA_TYPE = '415 Unsupported Media Type'.freeze
|
60
|
+
|
61
|
+
STATUS_INTERNAL_SERVER_ERROR = '500 Internal Server Error'.freeze
|
62
|
+
STATUS_NOT_IMPLEMENTED = '501 Not Implemented'.freeze
|
63
|
+
STATUS_SERVICE_UNAVAILABLE = '503 Service Unavailable'.freeze
|
64
|
+
|
65
|
+
# cookies
|
66
|
+
COOKIE = 'Cookie'.freeze
|
67
|
+
SET_COOKIE = 'Set-Cookie'.freeze
|
68
|
+
COOKIE_EXPIRED_TIME = Time.at(0).freeze
|
69
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# StandardError extensions.
|
2
|
+
class StandardError
|
3
|
+
# Returns the HTTP status code associated with the error class.
|
4
|
+
def http_status
|
5
|
+
ServerSide::HTTP::STATUS_INTERNAL_SERVER_ERROR
|
6
|
+
end
|
7
|
+
|
8
|
+
# Sets the HTTP status code associated with the error class.
|
9
|
+
def self.set_http_status(value)
|
10
|
+
define_method(:http_status) {value}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ServerSide::HTTP
|
15
|
+
# This error is raised when a malformed request is encountered.
|
16
|
+
class MalformedRequestError < RuntimeError
|
17
|
+
set_http_status STATUS_BAD_REQUEST
|
18
|
+
end
|
19
|
+
|
20
|
+
# This error is raised when an invalid file is referenced.
|
21
|
+
class FileNotFoundError < RuntimeError
|
22
|
+
set_http_status STATUS_NOT_FOUND
|
23
|
+
end
|
24
|
+
end
|