serverside 0.3.1 → 0.4.1
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/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
|