serverside 0.2.9 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +56 -0
- data/Rakefile +12 -52
- data/bin/serverside +1 -1
- data/lib/serverside/application.rb +2 -1
- data/lib/serverside/caching.rb +62 -50
- data/lib/serverside/controllers.rb +91 -0
- data/lib/serverside/core_ext.rb +6 -0
- data/lib/serverside/daemon.rb +25 -5
- data/lib/serverside/request.rb +17 -11
- data/lib/serverside/routing.rb +11 -10
- data/lib/serverside/server.rb +14 -6
- data/lib/serverside/static.rb +7 -18
- data/lib/serverside/template.rb +20 -12
- data/spec/caching_spec.rb +318 -0
- data/spec/cluster_spec.rb +140 -0
- data/{test/spec → spec}/connection_spec.rb +4 -4
- data/{test/spec/controller_spec.rb → spec/controllers_spec.rb} +15 -12
- data/{test/spec → spec}/core_ext_spec.rb +23 -18
- data/spec/daemon_spec.rb +99 -0
- data/{test/spec → spec}/request_spec.rb +45 -45
- data/spec/routing_spec.rb +240 -0
- data/spec/server_spec.rb +40 -0
- data/spec/static_spec.rb +279 -0
- data/spec/template_spec.rb +129 -0
- metadata +21 -35
- data/lib/serverside/controller.rb +0 -67
- data/test/functional/primitive_static_server_test.rb +0 -61
- data/test/functional/request_body_test.rb +0 -93
- data/test/functional/routing_server.rb +0 -14
- data/test/functional/routing_server_test.rb +0 -41
- data/test/functional/static_profile.rb +0 -17
- data/test/functional/static_rfuzz.rb +0 -31
- data/test/functional/static_server.rb +0 -7
- data/test/functional/static_server_test.rb +0 -31
- data/test/spec/caching_spec.rb +0 -139
- data/test/test_helper.rb +0 -2
- data/test/unit/cluster_test.rb +0 -129
- data/test/unit/connection_test.rb +0 -48
- data/test/unit/core_ext_test.rb +0 -46
- data/test/unit/daemon_test.rb +0 -75
- data/test/unit/request_test.rb +0 -278
- data/test/unit/routing_test.rb +0 -171
- data/test/unit/server_test.rb +0 -28
- data/test/unit/static_test.rb +0 -171
- data/test/unit/template_test.rb +0 -78
data/CHANGELOG
CHANGED
@@ -1,3 +1,59 @@
|
|
1
|
+
==0.3.0
|
2
|
+
|
3
|
+
* Disabled cluster_spec and parts of daemon_spec for now due to strange forking behavior.
|
4
|
+
|
5
|
+
* Removed static file cache from Static.
|
6
|
+
|
7
|
+
* Fixed daemon_spec to work correctly.
|
8
|
+
|
9
|
+
* Removed all tests, moved specs to /spec, and fixed rakefile.
|
10
|
+
|
11
|
+
* Converted cluster_test to cluster_spec.
|
12
|
+
|
13
|
+
* Converted daemon_test to daemon_spec.
|
14
|
+
|
15
|
+
* Updated specs to work with RSpec 0.7.
|
16
|
+
|
17
|
+
* Changed rake spec task to do coverage as well.
|
18
|
+
|
19
|
+
* Renamed controller.rb to controllers.rb.
|
20
|
+
|
21
|
+
* Added String.underscore method.
|
22
|
+
|
23
|
+
* Renamed ServerSide::Controller.process to response.
|
24
|
+
|
25
|
+
* Improved HTTP caching code to better conform to the HTTP spec. Also improved the validate_cache method to work without a block as well.
|
26
|
+
|
27
|
+
* Removed unit tests that were converted to specs.
|
28
|
+
|
29
|
+
* Router now excepts handlers to return non-nil if the request was handled. Otherwise, the request goes to the next handler with an appropriate rule.
|
30
|
+
|
31
|
+
* Improved spec coverage for caching.
|
32
|
+
|
33
|
+
* Wrote Server spec.
|
34
|
+
|
35
|
+
* Wrote Router spec.
|
36
|
+
|
37
|
+
* Refactored HTTP::Server to allow creating instances without starting them.
|
38
|
+
|
39
|
+
* Fixed Router.routes_defined? to return non-nil if default route is defined.
|
40
|
+
|
41
|
+
* Renamed Router.has_routes? to routes_defined?.
|
42
|
+
|
43
|
+
* Fixed Static.serve_template and Static.serve_static to work correctly (render templates.)
|
44
|
+
|
45
|
+
* Removed deprecated code in Template.render.
|
46
|
+
|
47
|
+
* Wrote Static spec.
|
48
|
+
|
49
|
+
* Fixed bug in serverside script - wrong call to route_default instead of default_route.
|
50
|
+
|
51
|
+
* Refactored ServerSide::Template and wrote template spec.
|
52
|
+
|
53
|
+
* Added documentation to Controller.
|
54
|
+
|
55
|
+
* Fixed Controller.mount to build a self.inherited method, and do the routing there.
|
56
|
+
|
1
57
|
==0.2.9
|
2
58
|
|
3
59
|
* Improved rake clean task.
|
data/Rakefile
CHANGED
@@ -2,12 +2,11 @@ require 'rake'
|
|
2
2
|
require 'rake/clean'
|
3
3
|
require 'rake/gempackagetask'
|
4
4
|
require 'rake/rdoctask'
|
5
|
-
require 'rake/testtask'
|
6
5
|
require 'fileutils'
|
7
6
|
include FileUtils
|
8
7
|
|
9
8
|
NAME = "serverside"
|
10
|
-
VERS = "0.
|
9
|
+
VERS = "0.3.0"
|
11
10
|
CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
|
12
11
|
RDOC_OPTS = ['--quiet', '--title', "ServerSide Documentation",
|
13
12
|
"--opname", "index.html",
|
@@ -47,7 +46,7 @@ spec = Gem::Specification.new do |s|
|
|
47
46
|
s.add_dependency('metaid')
|
48
47
|
s.required_ruby_version = '>= 1.8.2'
|
49
48
|
|
50
|
-
s.files = %w(COPYING README Rakefile) + Dir.glob("{bin,doc,
|
49
|
+
s.files = %w(COPYING README Rakefile) + Dir.glob("{bin,doc,spec,lib}/**/*")
|
51
50
|
|
52
51
|
s.require_path = "lib"
|
53
52
|
s.bindir = "bin"
|
@@ -70,60 +69,23 @@ end
|
|
70
69
|
desc 'Update docs and upload to rubyforge.org'
|
71
70
|
task :doc_rforge do
|
72
71
|
sh %{rake doc}
|
73
|
-
sh %{scp -r doc
|
74
|
-
end
|
75
|
-
|
76
|
-
desc 'Run unit tests'
|
77
|
-
Rake::TestTask.new('test_unit') do |t|
|
78
|
-
t.libs << 'test'
|
79
|
-
t.pattern = 'test/unit/*_test.rb'
|
80
|
-
t.verbose = true
|
81
|
-
end
|
82
|
-
|
83
|
-
desc 'Run functional tests'
|
84
|
-
Rake::TestTask.new('test_functional') do |t|
|
85
|
-
t.libs << 'test'
|
86
|
-
t.pattern = 'test/functional/*_test.rb'
|
87
|
-
t.verbose = true
|
88
|
-
end
|
89
|
-
|
90
|
-
desc 'Run specification tests'
|
91
|
-
task :spec do
|
92
|
-
sh %{spec test/spec/*_spec.rb}
|
93
|
-
end
|
94
|
-
|
95
|
-
desc 'Run rcov'
|
96
|
-
task :rcov do
|
97
|
-
sh %{rcov test/unit/*_test.rb test/functional/*_test.rb}
|
98
|
-
end
|
99
|
-
|
100
|
-
desc 'Run all tests'
|
101
|
-
Rake::TestTask.new('test') do |t|
|
102
|
-
t.libs << 'test'
|
103
|
-
t.pattern = 'test/**/*_test.rb'
|
104
|
-
t.verbose = true
|
105
|
-
end
|
106
|
-
|
107
|
-
desc 'Run all tests, specs and finish with rcov'
|
108
|
-
task :aok do
|
109
|
-
sh %{rake rcov}
|
110
|
-
sh %{rake spec}
|
72
|
+
sh %{scp -r doc/* ciconia@rubyforge.org:/var/www/gforge-projects/serverside}
|
111
73
|
end
|
112
74
|
|
113
75
|
require 'spec/rake/spectask'
|
114
76
|
|
115
|
-
desc "Run specs
|
116
|
-
Spec::Rake::SpecTask.new('
|
117
|
-
t.spec_files = FileList['
|
77
|
+
desc "Run specs with coverage"
|
78
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
79
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
118
80
|
t.rcov = true
|
119
81
|
end
|
120
82
|
|
121
|
-
require 'spec/rake/rcov_verify'
|
83
|
+
#require 'spec/rake/rcov_verify'
|
122
84
|
|
123
|
-
RCov::VerifyTask.new(:rcov_verify => :rcov) do |t|
|
124
|
-
t.threshold = 95.4 # Make sure you have rcov 0.7 or higher!
|
125
|
-
t.index_html = '
|
126
|
-
end
|
85
|
+
#RCov::VerifyTask.new(:rcov_verify => :rcov) do |t|
|
86
|
+
# t.threshold = 95.4 # Make sure you have rcov 0.7 or higher!
|
87
|
+
# t.index_html = 'coverage/index.html'
|
88
|
+
#end
|
127
89
|
|
128
90
|
##############################################################################
|
129
91
|
# Statistics
|
@@ -131,9 +93,7 @@ end
|
|
131
93
|
|
132
94
|
STATS_DIRECTORIES = [
|
133
95
|
%w(Code lib/),
|
134
|
-
%w(
|
135
|
-
%w(Functional\ tests test/functional),
|
136
|
-
%w(Specification\ tests test/spec)
|
96
|
+
%w(Specification\ tests spec)
|
137
97
|
].collect { |name, dir| [ name, "./#{dir}" ] }.select { |name, dir| File.directory?(dir) }
|
138
98
|
|
139
99
|
desc "Report code statistics (KLOCs, etc) from the application"
|
data/bin/serverside
CHANGED
@@ -76,7 +76,7 @@ if $cmd == 'serve'
|
|
76
76
|
puts "Serving at #{$cmd_config[:host]}:#{$cmd_config[:ports].begin}..."
|
77
77
|
trap('INT') {exit}
|
78
78
|
ServerSide::HTTP::Server.new($cmd_config[:host], $cmd_config[:ports].begin,
|
79
|
-
ServerSide::Router)
|
79
|
+
ServerSide::Router).start
|
80
80
|
else
|
81
81
|
ServerSide::Application.daemonize($cmd_config, $cmd)
|
82
82
|
end
|
@@ -15,7 +15,8 @@ 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::HTTP::Server.new(
|
18
|
+
ServerSide::HTTP::Server.new(
|
19
|
+
config[:host], port, ServerSide::Router).start
|
19
20
|
end
|
20
21
|
meta_def(:ports) {config[:ports]}
|
21
22
|
end
|
data/lib/serverside/caching.rb
CHANGED
@@ -4,21 +4,39 @@ module ServerSide
|
|
4
4
|
module HTTP
|
5
5
|
# This module implements HTTP cache negotiation with a client.
|
6
6
|
module Caching
|
7
|
+
# HTTP headers
|
7
8
|
ETAG = 'ETag'.freeze
|
9
|
+
LAST_MODIFIED = 'Last-Modified'.freeze
|
10
|
+
EXPIRES = 'Expires'.freeze
|
8
11
|
CACHE_CONTROL = 'Cache-Control'.freeze
|
9
|
-
|
12
|
+
VARY = 'Vary'.freeze
|
13
|
+
|
10
14
|
IF_NONE_MATCH = 'If-None-Match'.freeze
|
11
|
-
ETAG_WILDCARD = '*'.freeze
|
12
15
|
IF_MODIFIED_SINCE = 'If-Modified-Since'.freeze
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
WILDCARD = '*'.freeze
|
17
|
+
|
18
|
+
# Header values
|
19
|
+
NO_CACHE = 'no-cache'.freeze
|
17
20
|
IF_NONE_MATCH_REGEXP = /^"?([^"]+)"?$/.freeze
|
18
|
-
|
19
|
-
#
|
20
|
-
|
21
|
-
|
21
|
+
|
22
|
+
# etags
|
23
|
+
EXPIRY_ETAG_REGEXP = /(\d+)-(\d+)/.freeze
|
24
|
+
EXPIRY_ETAG_FORMAT = "%d-%d".freeze
|
25
|
+
ETAG_QUOTE_FORMAT = '"%s"'.freeze
|
26
|
+
|
27
|
+
# 304 formats
|
28
|
+
NOT_MODIFIED_CLOSE = "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nConnection: close\r\nContent-Length: 0\r\n\r\n".freeze
|
29
|
+
NOT_MODIFIED_PERSIST = "HTTP/1.1 304 Not Modified\r\nDate: %s\r\nContent-Length: 0\r\n\r\n".freeze
|
30
|
+
|
31
|
+
def disable_caching
|
32
|
+
@response_headers[CACHE_CONTROL] = NO_CACHE
|
33
|
+
@response_headers.delete(ETAG)
|
34
|
+
@response_headers.delete(LAST_MODIFIED)
|
35
|
+
@response_headers.delete(EXPIRES)
|
36
|
+
@response_headers.delete(VARY)
|
37
|
+
end
|
38
|
+
|
39
|
+
def etag_validators
|
22
40
|
h = @headers[IF_NONE_MATCH]
|
23
41
|
return [] unless h
|
24
42
|
h.split(',').inject([]) do |m, i|
|
@@ -26,54 +44,48 @@ module ServerSide
|
|
26
44
|
end
|
27
45
|
end
|
28
46
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
47
|
+
def valid_etag?(etag = nil)
|
48
|
+
if etag
|
49
|
+
etag_validators.each {|e| return true if e == etag || e == WILDCARD}
|
50
|
+
else
|
51
|
+
etag_validators.each do |e|
|
52
|
+
return true if e == WILDCARD ||
|
53
|
+
((e =~ EXPIRY_ETAG_REGEXP) && (Time.at($2.to_i) > Time.now))
|
54
|
+
end
|
55
|
+
end
|
34
56
|
nil
|
35
57
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
(none_match && (none_match =~ /\*|"#{etag}"/)) ||
|
45
|
-
(modified_since && (modified_since == http_stamp))
|
58
|
+
|
59
|
+
def expiry_etag(stamp, max_age)
|
60
|
+
EXPIRY_ETAG_FORMAT % [stamp.to_i, (stamp + max_age).to_i]
|
61
|
+
end
|
62
|
+
|
63
|
+
def valid_stamp?(stamp)
|
64
|
+
return true if (modified_since = @headers[IF_MODIFIED_SINCE]) &&
|
65
|
+
(modified_since == stamp.httpdate)
|
46
66
|
end
|
47
67
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# validate_cache("1234-5678", Time.now, 360) do
|
55
|
-
# send_response(200, "text/html", body)
|
56
|
-
# end
|
57
|
-
def validate_cache(etag, stamp, max_age = DEFAULT_MAX_AGE, &block)
|
58
|
-
http_stamp = stamp.httpdate
|
59
|
-
if valid_client_cache?(etag, http_stamp)
|
60
|
-
send_not_modified(etag, http_stamp, max_age)
|
68
|
+
def validate_cache(stamp, max_age, etag = nil,
|
69
|
+
cache_control = nil, vary = nil, &block)
|
70
|
+
|
71
|
+
if valid_etag?(etag) || valid_stamp?(stamp)
|
72
|
+
send_not_modified_response
|
73
|
+
true
|
61
74
|
else
|
62
|
-
@response_headers[ETAG] =
|
63
|
-
|
64
|
-
@response_headers[
|
65
|
-
|
75
|
+
@response_headers[ETAG] = ETAG_QUOTE_FORMAT %
|
76
|
+
[etag || expiry_etag(stamp, max_age)]
|
77
|
+
@response_headers[LAST_MODIFIED] = stamp.httpdate
|
78
|
+
@response_headers[EXPIRES] = (stamp + max_age).httpdate
|
79
|
+
@response_headers[CACHE_CONTROL] = cache_control if cache_control
|
80
|
+
@response_headers[VARY] = vary if vary
|
81
|
+
block ? block.call : nil
|
66
82
|
end
|
67
83
|
end
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
@socket << ((@persistent ? NOT_MODIFIED_PERSIST : NOT_MODIFIED_CLOSE) %
|
73
|
-
[Time.now.httpdate, http_time, etag, max_age])
|
84
|
+
|
85
|
+
def send_not_modified_response
|
86
|
+
@socket << ((@persistent ? NOT_MODIFIED_PERSIST : NOT_MODIFIED_CLOSE) %
|
87
|
+
Time.now.httpdate)
|
74
88
|
end
|
75
89
|
end
|
76
90
|
end
|
77
91
|
end
|
78
|
-
|
79
|
-
__END__
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'routing')
|
2
|
+
require 'rubygems'
|
3
|
+
require 'metaid'
|
4
|
+
|
5
|
+
module ServerSide
|
6
|
+
# Implements a basic controller class for handling requests. Controllers can
|
7
|
+
# be mounted by using the Controller.mount
|
8
|
+
class Controller
|
9
|
+
# Creates a subclass of Controller which adds a routing rule when
|
10
|
+
# subclassed. For example:
|
11
|
+
#
|
12
|
+
# class MyController < ServerSide::Controller.mount('/ohmy')
|
13
|
+
# def response
|
14
|
+
# render('Hi there!', 'text/plain')
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# You can of course route according to any rule as specified in
|
19
|
+
# ServerSide::Router.route, including passing a block as a rule, e.g.:
|
20
|
+
#
|
21
|
+
# class MyController < ServerSide::Controller.mount {@headers['Accept'] =~ /wap/}
|
22
|
+
# ...
|
23
|
+
# end
|
24
|
+
def self.mount(rule = nil, &block)
|
25
|
+
rule ||= block
|
26
|
+
raise ArgumentError, "No routing rule specified." if rule.nil?
|
27
|
+
Class.new(self) do
|
28
|
+
meta_def(:inherited) do |sub_class|
|
29
|
+
ServerSide::Router.route(rule) {sub_class.new(self)}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Initialize a new controller instance. Sets @request to the request object
|
35
|
+
# and copies both the request path and parameters to instance variables.
|
36
|
+
# After calling response, this method checks whether a response has been sent
|
37
|
+
# (rendered), and if not, invokes the render_default method.
|
38
|
+
def initialize(request)
|
39
|
+
@request = request
|
40
|
+
@path = request.path
|
41
|
+
@parameters = request.parameters
|
42
|
+
response
|
43
|
+
render_default if not @rendered
|
44
|
+
end
|
45
|
+
|
46
|
+
# Renders the response. This method should be overriden.
|
47
|
+
def response
|
48
|
+
end
|
49
|
+
|
50
|
+
# Sends a default response.
|
51
|
+
def render_default
|
52
|
+
@request.send_response(200, 'text/plain', 'no response.')
|
53
|
+
end
|
54
|
+
|
55
|
+
# Sends a response and sets @rendered to true.
|
56
|
+
def render(body, content_type)
|
57
|
+
@request.send_response(200, content_type, body)
|
58
|
+
@rendered = true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
__END__
|
64
|
+
|
65
|
+
class ServerSide::ActionController < ServerSide::Controller
|
66
|
+
def self.default_routing_rule
|
67
|
+
if name.split('::').last =~ /(.+)Controller$/
|
68
|
+
controller = Inflector.underscore($1)
|
69
|
+
{:path => ["/#{controller}", "/#{controller}/:action", "/#{controller}/:action/:id"]}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.inherited(c)
|
74
|
+
routing_rule = c.respond_to?(:routing_rule) ?
|
75
|
+
c.routing_rule : c.default_routing_rule
|
76
|
+
if routing_rule
|
77
|
+
ServerSide::Router.route(routing_rule) {c.new(self)}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.route(arg = nil, &block)
|
82
|
+
rule = arg || block
|
83
|
+
meta_def(:get_route) {rule}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class MyController < ActionController
|
88
|
+
route "hello"
|
89
|
+
end
|
90
|
+
|
91
|
+
p MyController.get_route
|
data/lib/serverside/core_ext.rb
CHANGED
@@ -15,6 +15,12 @@ class String
|
|
15
15
|
def /(o)
|
16
16
|
File.join(self, o.to_s)
|
17
17
|
end
|
18
|
+
|
19
|
+
# Converts camel-cased phrases to underscored phrases.
|
20
|
+
def underscore
|
21
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
22
|
+
tr("-", "_").downcase
|
23
|
+
end
|
18
24
|
end
|
19
25
|
|
20
26
|
# Symbol extensions and overrides.
|
data/lib/serverside/daemon.rb
CHANGED
@@ -1,5 +1,15 @@
|
|
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
|
+
|
3
13
|
# The Daemon module takes care of starting and stopping daemons.
|
4
14
|
module Daemon
|
5
15
|
WorkingDirectory = FileUtils.pwd
|
@@ -24,6 +34,10 @@ module Daemon
|
|
24
34
|
rescue
|
25
35
|
raise 'Pid not found. Is the daemon started?'
|
26
36
|
end
|
37
|
+
|
38
|
+
def self.remove(daemon)
|
39
|
+
FileUtils.rm(daemon.pid_fn) if File.file?(daemon.pid_fn)
|
40
|
+
end
|
27
41
|
end
|
28
42
|
|
29
43
|
# Controls a daemon according to the supplied command or command-line
|
@@ -50,18 +64,24 @@ module Daemon
|
|
50
64
|
PidFile.store(daemon, Process.pid)
|
51
65
|
Dir.chdir WorkingDirectory
|
52
66
|
File.umask 0000
|
53
|
-
STDIN.reopen "/dev/null"
|
54
|
-
STDOUT.reopen "/dev/null", "a"
|
55
|
-
STDERR.reopen STDOUT
|
67
|
+
# STDIN.reopen "/dev/null"
|
68
|
+
# STDOUT.reopen "/dev/null", "a"
|
69
|
+
# STDERR.reopen STDOUT
|
56
70
|
trap("TERM") {daemon.stop; exit}
|
57
71
|
daemon.start
|
58
72
|
end
|
59
73
|
end
|
60
|
-
|
74
|
+
|
61
75
|
# Stops the daemon by sending it a TERM signal.
|
62
76
|
def self.stop(daemon)
|
63
77
|
pid = PidFile.recall(daemon)
|
64
|
-
FileUtils.rm(daemon.pid_fn)
|
65
78
|
pid && Process.kill("TERM", pid)
|
79
|
+
PidFile.remove(daemon)
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.alive?(daemon)
|
83
|
+
pid = PidFile.recall(daemon) rescue nil
|
84
|
+
return nil if !pid
|
85
|
+
`ps #{pid}` =~ /#{pid}/ ? true : nil
|
66
86
|
end
|
67
87
|
end
|
data/lib/serverside/request.rb
CHANGED
@@ -123,15 +123,16 @@ module ServerSide
|
|
123
123
|
@parameters.merge! parse_parameters(@body)
|
124
124
|
elsif @content_type =~ MULTIPART_REGEXP
|
125
125
|
boundary = "--#$1"
|
126
|
-
|
126
|
+
r = /(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r\n/m
|
127
|
+
@body.split(r).each do |pt|
|
127
128
|
headers, payload = pt.split("\r\n\r\n", 2)
|
128
129
|
atts = {}
|
129
130
|
if headers =~ CONTENT_DISPOSITION_REGEXP
|
130
|
-
$1.split(';').map
|
131
|
+
$1.split(';').map do |part|
|
131
132
|
if part =~ FIELD_ATTRIBUTE_REGEXP
|
132
133
|
atts[$1.to_sym] = $2
|
133
134
|
end
|
134
|
-
|
135
|
+
end
|
135
136
|
end
|
136
137
|
if headers =~ CONTENT_TYPE_REGEXP
|
137
138
|
atts[:type] = $1
|
@@ -150,16 +151,17 @@ module ServerSide
|
|
150
151
|
@response_headers.merge!(headers) if headers
|
151
152
|
h = @response_headers.inject('') {|m, kv| m << (HEADER % kv)}
|
152
153
|
|
153
|
-
# calculate content_length if needed. if we dont have the
|
154
|
-
# we consider the response as a streaming response,
|
155
|
-
# will not be persistent.
|
154
|
+
# calculate content_length if needed. if we dont have the
|
155
|
+
# content_length, we consider the response as a streaming response,
|
156
|
+
# and so the connection will not be persistent.
|
156
157
|
content_length = body.length if content_length.nil? && body
|
157
158
|
@persistent = false if content_length.nil?
|
158
159
|
|
159
160
|
# Select the right format to use according to circumstances.
|
160
161
|
@socket << ((@persistent ? STATUS_PERSIST :
|
161
162
|
(body ? STATUS_CLOSE : STATUS_STREAM)) %
|
162
|
-
[status, Time.now.httpdate, content_type, h, @response_cookies,
|
163
|
+
[status, Time.now.httpdate, content_type, h, @response_cookies,
|
164
|
+
content_length])
|
163
165
|
@socket << body if body
|
164
166
|
rescue
|
165
167
|
@persistent = false
|
@@ -168,8 +170,10 @@ module ServerSide
|
|
168
170
|
CONTENT_DISPOSITION = 'Content-Disposition'.freeze
|
169
171
|
CONTENT_DESCRIPTION = 'Content-Description'.freeze
|
170
172
|
|
171
|
-
def send_file(content, content_type, disposition = :inline,
|
172
|
-
|
173
|
+
def send_file(content, content_type, disposition = :inline,
|
174
|
+
filename = nil, description = nil)
|
175
|
+
disposition = filename ?
|
176
|
+
"#{disposition}; filename=#{filename}" : disposition
|
173
177
|
@response_headers[CONTENT_DISPOSITION] = disposition
|
174
178
|
@response_headers[CONTENT_DESCRIPTION] = description if description
|
175
179
|
send_response(200, content_type, content)
|
@@ -192,10 +196,12 @@ module ServerSide
|
|
192
196
|
# Sets a cookie to be included in the response.
|
193
197
|
def set_cookie(name, value, expires)
|
194
198
|
@response_cookies ||= ""
|
195
|
-
@response_cookies <<
|
199
|
+
@response_cookies <<
|
200
|
+
(SET_COOKIE % [name, value.to_s.uri_escape, expires.rfc2822])
|
196
201
|
end
|
197
202
|
|
198
|
-
# Marks a cookie as deleted. The cookie is given an expires stamp in
|
203
|
+
# Marks a cookie as deleted. The cookie is given an expires stamp in
|
204
|
+
# the past.
|
199
205
|
def delete_cookie(name)
|
200
206
|
set_cookie(name, nil, COOKIE_EXPIRED_TIME)
|
201
207
|
end
|
data/lib/serverside/routing.rb
CHANGED
@@ -22,9 +22,11 @@ module ServerSide
|
|
22
22
|
# Routing rules are evaluated backwards, so the rules should be ordered
|
23
23
|
# from the general to the specific.
|
24
24
|
class Router < HTTP::Request
|
25
|
+
@@rules = []
|
26
|
+
|
25
27
|
# Returns true if routes were defined.
|
26
|
-
def self.
|
27
|
-
|
28
|
+
def self.routes_defined?
|
29
|
+
!@@rules.empty? || @@default_route
|
28
30
|
end
|
29
31
|
|
30
32
|
# Adds a routing rule. The normalized rule is a hash containing keys (acting
|
@@ -36,7 +38,6 @@ module ServerSide
|
|
36
38
|
#
|
37
39
|
# ServerSide.route(lambda{path = 'mypage'}) {serve_static('mypage.html')}
|
38
40
|
def self.route(rule, &block)
|
39
|
-
@@rules ||= []
|
40
41
|
rule = {:path => rule} unless (Hash === rule) || (Proc === rule)
|
41
42
|
@@rules.unshift [rule, block]
|
42
43
|
compile_rules
|
@@ -45,7 +46,6 @@ module ServerSide
|
|
45
46
|
# Compiles all rules into a respond method that is invoked when a request
|
46
47
|
# is received.
|
47
48
|
def self.compile_rules
|
48
|
-
@@rules ||= []
|
49
49
|
code = @@rules.inject('lambda {') {|m, r| m << rule_to_statement(r[0], r[1])}
|
50
50
|
code << 'default_handler}'
|
51
51
|
define_method(:respond, &eval(code))
|
@@ -66,15 +66,15 @@ module ServerSide
|
|
66
66
|
end
|
67
67
|
}.join('&&')
|
68
68
|
end
|
69
|
-
"
|
69
|
+
"if #{cond} && (r = #{proc_tag}); return r; end\n"
|
70
70
|
end
|
71
71
|
|
72
|
-
# Pattern for finding parameters inside patterns. Parameters are parts
|
73
|
-
# pattern, which the routing pre-processor turns into sub-regexp
|
74
|
-
# used to extract parameter values from the pattern.
|
72
|
+
# Pattern for finding parameters inside patterns. Parameters are parts
|
73
|
+
# of the pattern, which the routing pre-processor turns into sub-regexp
|
74
|
+
# that are used to extract parameter values from the pattern.
|
75
75
|
#
|
76
|
-
# For example, matching '/controller/show' against '/controller/:action'
|
77
|
-
# give us @parameters[:action] #=> "show"
|
76
|
+
# For example, matching '/controller/show' against '/controller/:action'
|
77
|
+
# will give us @parameters[:action] #=> "show"
|
78
78
|
ParamRegexp = /(?::([a-z]+))/
|
79
79
|
|
80
80
|
# Returns the condition part for the key and value specified. The key is the
|
@@ -117,6 +117,7 @@ module ServerSide
|
|
117
117
|
|
118
118
|
# Sets the default handler for incoming requests.
|
119
119
|
def self.default_route(&block)
|
120
|
+
@@default_route = block
|
120
121
|
define_method(:default_handler, &block)
|
121
122
|
compile_rules
|
122
123
|
end
|
data/lib/serverside/server.rb
CHANGED
@@ -6,13 +6,21 @@ module ServerSide
|
|
6
6
|
# designed to support both HTTP 1.1 persistent connections, and HTTP streaming
|
7
7
|
# for applications which use Comet techniques.
|
8
8
|
class Server
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
# processing.
|
9
|
+
attr_reader :listener
|
10
|
+
|
11
|
+
# Creates a new server by opening a listening socket.
|
13
12
|
def initialize(host, port, request_class)
|
14
|
-
@
|
15
|
-
|
13
|
+
@request_class = request_class
|
14
|
+
@listener = TCPServer.new(host, port)
|
15
|
+
end
|
16
|
+
|
17
|
+
# starts an accept loop. When a new connection is accepted, a new
|
18
|
+
# instance of the supplied connection class is instantiated and passed
|
19
|
+
# the connection for processing.
|
20
|
+
def start
|
21
|
+
while true
|
22
|
+
Connection.new(@listener.accept, @request_class)
|
23
|
+
end
|
16
24
|
end
|
17
25
|
end
|
18
26
|
end
|