sanford 0.10.1 → 0.11.0
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/Gemfile +1 -1
- data/README.md +41 -56
- data/Rakefile +0 -1
- data/bench/client.rb +8 -3
- data/bench/{services.rb → config.sanford} +11 -6
- data/bench/{runner.rb → report.rb} +2 -2
- data/bench/report.txt +32 -32
- data/lib/sanford/cli.rb +42 -28
- data/lib/sanford/config_file.rb +79 -0
- data/lib/sanford/{worker.rb → connection_handler.rb} +28 -20
- data/lib/sanford/error_handler.rb +7 -7
- data/lib/sanford/pid_file.rb +42 -0
- data/lib/sanford/process.rb +136 -0
- data/lib/sanford/process_signal.rb +20 -0
- data/lib/sanford/route.rb +48 -0
- data/lib/sanford/router.rb +36 -0
- data/lib/sanford/runner.rb +30 -58
- data/lib/sanford/sanford_runner.rb +19 -9
- data/lib/sanford/server.rb +211 -42
- data/lib/sanford/server_data.rb +47 -0
- data/lib/sanford/service_handler.rb +8 -46
- data/lib/sanford/template_source.rb +19 -2
- data/lib/sanford/test_runner.rb +27 -28
- data/lib/sanford/version.rb +1 -1
- data/lib/sanford.rb +1 -23
- data/sanford.gemspec +4 -5
- data/test/helper.rb +3 -20
- data/test/support/app_server.rb +142 -0
- data/test/support/config.sanford +7 -0
- data/test/support/config_invalid_run.sanford +3 -0
- data/test/support/config_no_run.sanford +0 -0
- data/test/support/fake_server_connection.rb +58 -0
- data/test/support/pid_file_spy.rb +19 -0
- data/test/support/template.erb +1 -0
- data/test/system/server_tests.rb +378 -0
- data/test/system/service_handler_tests.rb +224 -0
- data/test/unit/cli_tests.rb +187 -0
- data/test/unit/config_file_tests.rb +59 -0
- data/test/unit/connection_handler_tests.rb +254 -0
- data/test/unit/error_handler_tests.rb +30 -35
- data/test/unit/pid_file_tests.rb +70 -0
- data/test/unit/process_signal_tests.rb +61 -0
- data/test/unit/process_tests.rb +428 -0
- data/test/unit/route_tests.rb +92 -0
- data/test/unit/router_tests.rb +65 -0
- data/test/unit/runner_tests.rb +61 -15
- data/test/unit/sanford_runner_tests.rb +162 -28
- data/test/unit/sanford_tests.rb +0 -8
- data/test/unit/server_data_tests.rb +87 -0
- data/test/unit/server_tests.rb +502 -21
- data/test/unit/service_handler_tests.rb +114 -219
- data/test/unit/template_engine_tests.rb +1 -1
- data/test/unit/template_source_tests.rb +56 -16
- data/test/unit/test_runner_tests.rb +206 -0
- metadata +67 -67
- data/bench/tasks.rb +0 -41
- data/lib/sanford/config.rb +0 -28
- data/lib/sanford/host.rb +0 -129
- data/lib/sanford/host_data.rb +0 -65
- data/lib/sanford/hosts.rb +0 -38
- data/lib/sanford/manager.rb +0 -275
- data/test/support/fake_connection.rb +0 -36
- data/test/support/helpers.rb +0 -17
- data/test/support/service_handlers.rb +0 -154
- data/test/support/services.rb +0 -123
- data/test/support/simple_client.rb +0 -62
- data/test/system/request_handling_tests.rb +0 -306
- data/test/unit/config_tests.rb +0 -56
- data/test/unit/host_data_tests.rb +0 -71
- data/test/unit/host_tests.rb +0 -141
- data/test/unit/hosts_tests.rb +0 -50
- data/test/unit/manager_tests.rb +0 -195
- data/test/unit/worker_tests.rb +0 -24
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'sanford/pid_file'
|
2
|
+
|
3
|
+
module Sanford
|
4
|
+
|
5
|
+
class Process
|
6
|
+
|
7
|
+
attr_reader :server, :name, :pid_file, :restart_cmd
|
8
|
+
attr_reader :server_ip, :server_port, :server_fd, :client_fds
|
9
|
+
|
10
|
+
def initialize(server, options = nil)
|
11
|
+
options ||= {}
|
12
|
+
@server = server
|
13
|
+
@logger = @server.logger
|
14
|
+
@name = "sanford-#{@server.name}"
|
15
|
+
@pid_file = PIDFile.new(@server.pid_file)
|
16
|
+
@restart_cmd = RestartCmd.new
|
17
|
+
|
18
|
+
@server_ip = ignore_if_blank(ENV['SANFORD_IP'])
|
19
|
+
@server_port = ignore_if_blank(ENV['SANFORD_PORT']){ |v| v.to_i }
|
20
|
+
@server_fd = ignore_if_blank(ENV['SANFORD_SERVER_FD']){ |v| v.to_i }
|
21
|
+
@listen_args = @server_fd ? [ @server_fd ] : [ @server_ip, @server_port ]
|
22
|
+
@listen_args.compact!
|
23
|
+
|
24
|
+
@client_fds = (ENV['SANFORD_CLIENT_FDS'] || "").split(',').map(&:to_i)
|
25
|
+
|
26
|
+
@daemonize = !!options[:daemonize]
|
27
|
+
@skip_daemonize = !!ignore_if_blank(ENV['SANFORD_SKIP_DAEMONIZE'])
|
28
|
+
end
|
29
|
+
|
30
|
+
def run
|
31
|
+
::Process.daemon(true) if self.daemonize?
|
32
|
+
log "Starting Sanford server for #{@server.name}..."
|
33
|
+
|
34
|
+
@server.listen(*@listen_args)
|
35
|
+
log "Listening on #{@server.ip}:#{@server.port}"
|
36
|
+
|
37
|
+
$0 = @name
|
38
|
+
@pid_file.write
|
39
|
+
log "PID: #{@pid_file.pid}"
|
40
|
+
|
41
|
+
::Signal.trap("TERM"){ @server.stop }
|
42
|
+
::Signal.trap("INT"){ @server.halt }
|
43
|
+
::Signal.trap("USR2"){ @server.pause }
|
44
|
+
|
45
|
+
thread = @server.start(@client_fds)
|
46
|
+
log "#{@server.name} server started and ready."
|
47
|
+
thread.join
|
48
|
+
exec_restart_cmd if @server.paused?
|
49
|
+
rescue StandardError => exception
|
50
|
+
log "Error: #{exception.message}"
|
51
|
+
log "#{@server.name} server never started."
|
52
|
+
ensure
|
53
|
+
@pid_file.remove
|
54
|
+
end
|
55
|
+
|
56
|
+
def daemonize?
|
57
|
+
@daemonize && !@skip_daemonize
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def log(message)
|
63
|
+
@logger.info "[Sanford] #{message}"
|
64
|
+
end
|
65
|
+
|
66
|
+
def exec_restart_cmd
|
67
|
+
log "Restarting #{@server.name} daemon..."
|
68
|
+
ENV['SANFORD_SERVER_FD'] = @server.file_descriptor.to_s
|
69
|
+
ENV['SANFORD_CLIENT_FDS'] = @server.client_file_descriptors.join(',')
|
70
|
+
ENV['SANFORD_SKIP_DAEMONIZE'] = 'yes'
|
71
|
+
@restart_cmd.exec
|
72
|
+
end
|
73
|
+
|
74
|
+
def ignore_if_blank(value, default = nil, &block)
|
75
|
+
block ||= proc{ |v| v }
|
76
|
+
value && !value.empty? ? block.call(value) : default
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
class RestartCmd
|
82
|
+
attr_reader :argv, :dir
|
83
|
+
|
84
|
+
def initialize
|
85
|
+
require 'rubygems'
|
86
|
+
@dir = get_pwd
|
87
|
+
@argv = [ Gem.ruby, $0, ARGV.dup ].flatten
|
88
|
+
end
|
89
|
+
|
90
|
+
def exec
|
91
|
+
Dir.chdir self.dir
|
92
|
+
Kernel.exec(*self.argv)
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
# Trick from puma/unicorn. Favor PWD because it contains an unresolved
|
98
|
+
# symlink. This is useful when restarting after deploying; the original
|
99
|
+
# directory may be removed, but the symlink is pointing to a new
|
100
|
+
# directory.
|
101
|
+
def get_pwd
|
102
|
+
env_stat = File.stat(ENV['PWD'])
|
103
|
+
pwd_stat = File.stat(Dir.pwd)
|
104
|
+
if env_stat.ino == pwd_stat.ino && env_stat.dev == pwd_stat.dev
|
105
|
+
ENV['PWD']
|
106
|
+
else
|
107
|
+
Dir.pwd
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# This is from puma for 1.8 compatibility. Ruby 1.9+ defines a
|
113
|
+
# `Process.daemon` for daemonizing processes. This defines the method when it
|
114
|
+
# isn't provided, i.e. Ruby 1.8.
|
115
|
+
unless ::Process.respond_to?(:daemon)
|
116
|
+
::Process.class_eval do
|
117
|
+
|
118
|
+
# Full explanation: http://www.steve.org.uk/Reference/Unix/faq_2.html#SEC16
|
119
|
+
def self.daemon(no_chdir = false, no_close = false)
|
120
|
+
exit if fork
|
121
|
+
::Process.setsid
|
122
|
+
exit if fork
|
123
|
+
Dir.chdir '/' unless no_chdir
|
124
|
+
if !no_close
|
125
|
+
null = File.open('/dev/null', 'w')
|
126
|
+
STDIN.reopen null
|
127
|
+
STDOUT.reopen null
|
128
|
+
STDERR.reopen null
|
129
|
+
end
|
130
|
+
return 0
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'sanford/pid_file'
|
2
|
+
|
3
|
+
module Sanford
|
4
|
+
|
5
|
+
class ProcessSignal
|
6
|
+
|
7
|
+
attr_reader :signal, :pid
|
8
|
+
|
9
|
+
def initialize(server, signal)
|
10
|
+
@signal = signal
|
11
|
+
@pid = PIDFile.new(server.pid_file).pid
|
12
|
+
end
|
13
|
+
|
14
|
+
def send
|
15
|
+
::Process.kill(@signal, @pid)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'sanford/sanford_runner'
|
2
|
+
|
3
|
+
module Sanford
|
4
|
+
|
5
|
+
class Route
|
6
|
+
|
7
|
+
attr_reader :name, :handler_class_name, :handler_class
|
8
|
+
|
9
|
+
def initialize(name, handler_class_name)
|
10
|
+
@name = name.to_s
|
11
|
+
@handler_class_name = handler_class_name
|
12
|
+
@handler_class = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate!
|
16
|
+
@handler_class = constantize_handler_class(@handler_class_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(request, server_data)
|
20
|
+
SanfordRunner.new(self.handler_class, request, server_data).run
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def constantize_handler_class(handler_class_name)
|
26
|
+
constantize(handler_class_name).tap do |handler_class|
|
27
|
+
raise(NoHandlerClassError.new(handler_class_name)) if !handler_class
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def constantize(class_name)
|
32
|
+
names = class_name.to_s.split('::').reject{ |name| name.empty? }
|
33
|
+
klass = names.inject(Object){ |constant, name| constant.const_get(name) }
|
34
|
+
klass == Object ? false : klass
|
35
|
+
rescue NameError
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
class NoHandlerClassError < RuntimeError
|
42
|
+
def initialize(handler_class_name)
|
43
|
+
super "Sanford couldn't find the service handler '#{handler_class_name}'" \
|
44
|
+
" - it doesn't exist or hasn't been required in yet."
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'sanford/route'
|
2
|
+
|
3
|
+
module Sanford
|
4
|
+
|
5
|
+
class Router
|
6
|
+
|
7
|
+
attr_reader :routes
|
8
|
+
|
9
|
+
def initialize(&block)
|
10
|
+
@service_handler_ns = nil
|
11
|
+
@routes = []
|
12
|
+
self.instance_eval(&block) if !block.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
def service_handler_ns(value = nil)
|
16
|
+
@view_handler_ns = value if !value.nil?
|
17
|
+
@view_handler_ns
|
18
|
+
end
|
19
|
+
|
20
|
+
def service(name, handler_name)
|
21
|
+
if self.service_handler_ns && !(handler_name =~ /^::/)
|
22
|
+
handler_name = "#{self.service_handler_ns}::#{handler_name}"
|
23
|
+
end
|
24
|
+
|
25
|
+
@routes.push(Sanford::Route.new(name, handler_name))
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect
|
29
|
+
reference = '0x0%x' % (self.object_id << 1)
|
30
|
+
"#<#{self.class}:#{reference} " \
|
31
|
+
"@service_handler_ns=#{self.service_handler_ns.inspect}>"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/sanford/runner.rb
CHANGED
@@ -1,75 +1,47 @@
|
|
1
|
-
require 'ostruct'
|
2
1
|
require 'sanford-protocol'
|
2
|
+
require 'sanford/logger'
|
3
|
+
require 'sanford/template_source'
|
3
4
|
|
4
5
|
module Sanford
|
5
6
|
|
6
|
-
|
7
|
+
class Runner
|
7
8
|
|
8
9
|
ResponseArgs = Struct.new(:status, :data)
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
extend ClassMethods
|
13
|
-
include InstanceMethods
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
module InstanceMethods
|
18
|
-
|
19
|
-
attr_reader :handler_class, :request, :logger, :handler
|
20
|
-
|
21
|
-
def initialize(handler_class, request, logger = nil)
|
22
|
-
@handler_class, @request = handler_class, request
|
23
|
-
@logger = logger || Sanford.config.logger
|
24
|
-
@handler = @handler_class.new(self)
|
25
|
-
self.init
|
26
|
-
end
|
27
|
-
|
28
|
-
def init
|
29
|
-
self.init!
|
30
|
-
end
|
31
|
-
|
32
|
-
def init!
|
33
|
-
end
|
34
|
-
|
35
|
-
def run
|
36
|
-
build_response catch_halt{ self.run! }
|
37
|
-
end
|
11
|
+
attr_reader :handler_class, :handler
|
12
|
+
attr_reader :request, :params, :logger, :template_source
|
38
13
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
# It's best to keep what `halt` and `catch_halt` return in the same format.
|
44
|
-
# Currently this is a `ResponseArgs` object. This is so no matter how the
|
45
|
-
# block returns (either by throwing or running normally), you get the same
|
46
|
-
# thing kind of object.
|
47
|
-
|
48
|
-
def halt(status, options = nil)
|
49
|
-
options = OpenStruct.new(options || {})
|
50
|
-
response_status = [ status, options.message ]
|
51
|
-
throw :halt, ResponseArgs.new(response_status, options.data)
|
52
|
-
end
|
53
|
-
|
54
|
-
def catch_halt(&block)
|
55
|
-
catch(:halt){ ResponseArgs.new(*block.call) }
|
56
|
-
end
|
57
|
-
|
58
|
-
protected
|
14
|
+
def initialize(handler_class)
|
15
|
+
@handler_class = handler_class
|
16
|
+
@handler = @handler_class.new(self)
|
17
|
+
end
|
59
18
|
|
60
|
-
|
61
|
-
|
62
|
-
|
19
|
+
def run
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
63
22
|
|
23
|
+
# It's best to keep what `halt` and `catch_halt` return in the same format.
|
24
|
+
# Currently this is a `ResponseArgs` object. This is so no matter how the
|
25
|
+
# block returns (either by throwing or running normally), you get the same
|
26
|
+
# thing kind of object.
|
27
|
+
|
28
|
+
def halt(status, options = nil)
|
29
|
+
options ||= {}
|
30
|
+
message = options[:message] || options['message']
|
31
|
+
response_status = [ status, message ]
|
32
|
+
response_data = options[:data] || options['data']
|
33
|
+
throw :halt, ResponseArgs.new(response_status, response_data)
|
64
34
|
end
|
65
35
|
|
66
|
-
|
36
|
+
private
|
67
37
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
38
|
+
def catch_halt(&block)
|
39
|
+
catch(:halt){ ResponseArgs.new(*block.call) }
|
40
|
+
end
|
72
41
|
|
42
|
+
def build_response(&block)
|
43
|
+
args = catch_halt(&block)
|
44
|
+
Sanford::Protocol::Response.new(args.status, args.data)
|
73
45
|
end
|
74
46
|
|
75
47
|
end
|
@@ -2,24 +2,34 @@ require 'sanford/runner'
|
|
2
2
|
|
3
3
|
module Sanford
|
4
4
|
|
5
|
-
class SanfordRunner
|
6
|
-
|
5
|
+
class SanfordRunner < Sanford::Runner
|
6
|
+
|
7
|
+
def initialize(handler_class, request, server_data)
|
8
|
+
@request = request
|
9
|
+
@params = @request.params
|
10
|
+
@logger = server_data.logger
|
11
|
+
@template_source = server_data.template_source
|
12
|
+
|
13
|
+
super(handler_class)
|
14
|
+
end
|
7
15
|
|
8
16
|
# call the handler init and the handler run - if the init halts, run won't
|
9
17
|
# be called.
|
10
18
|
|
11
|
-
def run
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
def run
|
20
|
+
build_response do
|
21
|
+
run_callbacks self.handler_class.before_callbacks
|
22
|
+
self.handler.init
|
23
|
+
return_value = self.handler.run
|
24
|
+
run_callbacks self.handler_class.after_callbacks
|
25
|
+
return_value
|
26
|
+
end
|
17
27
|
end
|
18
28
|
|
19
29
|
private
|
20
30
|
|
21
31
|
def run_callbacks(callbacks)
|
22
|
-
callbacks.each{|proc| self.handler.instance_eval(&proc) }
|
32
|
+
callbacks.each{ |proc| self.handler.instance_eval(&proc) }
|
23
33
|
end
|
24
34
|
|
25
35
|
end
|
data/lib/sanford/server.rb
CHANGED
@@ -1,62 +1,181 @@
|
|
1
1
|
require 'dat-tcp'
|
2
|
-
require '
|
2
|
+
require 'ns-options'
|
3
|
+
require 'ns-options/boolean'
|
3
4
|
require 'sanford-protocol'
|
4
|
-
|
5
|
-
require 'sanford/
|
6
|
-
require 'sanford/
|
5
|
+
require 'socket'
|
6
|
+
require 'sanford/logger'
|
7
|
+
require 'sanford/router'
|
8
|
+
require 'sanford/server_data'
|
9
|
+
require 'sanford/template_source'
|
10
|
+
require 'sanford/connection_handler'
|
7
11
|
|
8
12
|
module Sanford
|
9
13
|
|
10
|
-
|
11
|
-
include DatTCP::Server
|
12
|
-
|
13
|
-
attr_reader :sanford_host, :sanford_host_data, :sanford_host_options
|
14
|
+
module Server
|
14
15
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
:verbose_logging => options.delete(:verbose)
|
21
|
-
}
|
22
|
-
super options
|
16
|
+
def self.included(klass)
|
17
|
+
klass.class_eval do
|
18
|
+
extend ClassMethods
|
19
|
+
include InstanceMethods
|
20
|
+
end
|
23
21
|
end
|
24
22
|
|
25
|
-
|
26
|
-
# communication, we have all the information we need to send up front and
|
27
|
-
# are closing the connection, so it doesn't need to buffer.
|
28
|
-
# See http://linux.die.net/man/7/tcp
|
23
|
+
module InstanceMethods
|
29
24
|
|
30
|
-
|
31
|
-
tcp_server.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
|
32
|
-
end
|
25
|
+
attr_reader :server_data, :dat_tcp_server
|
33
26
|
|
34
|
-
|
35
|
-
|
36
|
-
|
27
|
+
def initialize
|
28
|
+
self.class.configuration.validate!
|
29
|
+
@server_data = ServerData.new(self.class.configuration.to_hash)
|
30
|
+
@dat_tcp_server = DatTCP::Server.new{ |socket| serve(socket) }
|
31
|
+
rescue InvalidError => exception
|
32
|
+
exception.set_backtrace(caller)
|
33
|
+
raise exception
|
34
|
+
end
|
35
|
+
|
36
|
+
def name
|
37
|
+
@server_data.name
|
38
|
+
end
|
39
|
+
|
40
|
+
def ip
|
41
|
+
@dat_tcp_server.ip
|
42
|
+
end
|
43
|
+
|
44
|
+
def port
|
45
|
+
@dat_tcp_server.port
|
46
|
+
end
|
47
|
+
|
48
|
+
def file_descriptor
|
49
|
+
@dat_tcp_server.file_descriptor
|
50
|
+
end
|
51
|
+
|
52
|
+
def client_file_descriptors
|
53
|
+
@dat_tcp_server.client_file_descriptors
|
54
|
+
end
|
55
|
+
|
56
|
+
def pid_file
|
57
|
+
@server_data.pid_file
|
58
|
+
end
|
59
|
+
|
60
|
+
def logger
|
61
|
+
@server_data.logger
|
62
|
+
end
|
63
|
+
|
64
|
+
def listen(*args)
|
65
|
+
args = [ @server_data.ip, @server_data.port ] if args.empty?
|
66
|
+
@dat_tcp_server.listen(*args) do |server_socket|
|
67
|
+
configure_tcp_server(server_socket)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def start(*args)
|
72
|
+
@dat_tcp_server.start(*args)
|
73
|
+
end
|
74
|
+
|
75
|
+
def pause(*args)
|
76
|
+
@dat_tcp_server.pause(*args)
|
77
|
+
end
|
78
|
+
|
79
|
+
def stop(*args)
|
80
|
+
@dat_tcp_server.stop(*args)
|
81
|
+
end
|
82
|
+
|
83
|
+
def halt(*args)
|
84
|
+
@dat_tcp_server.halt(*args)
|
85
|
+
end
|
86
|
+
|
87
|
+
def paused?
|
88
|
+
@dat_tcp_server.listening? && !@dat_tcp_server.running?
|
89
|
+
end
|
37
90
|
|
38
|
-
|
39
|
-
# create a new instance of the handler for every request.
|
40
|
-
# When using TCP_CORK, you "cork" the socket, handle it and then "uncork"
|
41
|
-
# it, see the `TCPCork` module for more info.
|
91
|
+
private
|
42
92
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
93
|
+
def serve(socket)
|
94
|
+
connection = Connection.new(socket)
|
95
|
+
if !keep_alive_connection?(connection)
|
96
|
+
Sanford::ConnectionHandler.new(@server_data, connection).run
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def keep_alive_connection?(connection)
|
101
|
+
@server_data.receives_keep_alive && connection.peek_data.empty?
|
102
|
+
end
|
103
|
+
|
104
|
+
# TCP_NODELAY is set to disable buffering. In the case of Sanford
|
105
|
+
# communication, we have all the information we need to send up front and
|
106
|
+
# are closing the connection, so it doesn't need to buffer.
|
107
|
+
# See http://linux.die.net/man/7/tcp
|
108
|
+
|
109
|
+
def configure_tcp_server(tcp_server)
|
110
|
+
tcp_server.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
|
47
111
|
end
|
112
|
+
|
48
113
|
end
|
49
114
|
|
50
|
-
|
115
|
+
module ClassMethods
|
116
|
+
|
117
|
+
def configuration
|
118
|
+
@configuration ||= Configuration.new
|
119
|
+
end
|
120
|
+
|
121
|
+
def name(*args)
|
122
|
+
self.configuration.name *args
|
123
|
+
end
|
124
|
+
|
125
|
+
def ip(*args)
|
126
|
+
self.configuration.ip *args
|
127
|
+
end
|
128
|
+
|
129
|
+
def port(*args)
|
130
|
+
self.configuration.port *args
|
131
|
+
end
|
132
|
+
|
133
|
+
def pid_file(*args)
|
134
|
+
self.configuration.pid_file *args
|
135
|
+
end
|
136
|
+
|
137
|
+
def receives_keep_alive(*args)
|
138
|
+
self.configuration.receives_keep_alive *args
|
139
|
+
end
|
140
|
+
|
141
|
+
def verbose_logging(*args)
|
142
|
+
self.configuration.verbose_logging *args
|
143
|
+
end
|
144
|
+
|
145
|
+
def logger(*args)
|
146
|
+
self.configuration.logger *args
|
147
|
+
end
|
148
|
+
|
149
|
+
def init(&block)
|
150
|
+
self.configuration.init_procs << block
|
151
|
+
end
|
152
|
+
|
153
|
+
def error(&block)
|
154
|
+
self.configuration.error_procs << block
|
155
|
+
end
|
156
|
+
|
157
|
+
def router(value = nil, &block)
|
158
|
+
self.configuration.router = value if !value.nil?
|
159
|
+
self.configuration.router.instance_eval(&block) if block
|
160
|
+
self.configuration.router
|
161
|
+
end
|
162
|
+
|
163
|
+
def template_source(*args)
|
164
|
+
self.configuration.template_source(*args)
|
165
|
+
end
|
166
|
+
|
167
|
+
def build_template_source(path, &block)
|
168
|
+
block ||= proc{ }
|
169
|
+
self.template_source TemplateSource.new(path).tap(&block)
|
170
|
+
end
|
51
171
|
|
52
|
-
def keep_alive_connection?(connection)
|
53
|
-
@sanford_host_data.keep_alive && connection.peek_data.empty?
|
54
172
|
end
|
55
173
|
|
56
174
|
class Connection
|
57
|
-
|
58
175
|
DEFAULT_TIMEOUT = 1
|
59
176
|
|
177
|
+
attr_reader :timeout
|
178
|
+
|
60
179
|
def initialize(socket)
|
61
180
|
@socket = socket
|
62
181
|
@connection = Sanford::Protocol::Connection.new(@socket)
|
@@ -80,11 +199,9 @@ module Sanford
|
|
80
199
|
def close_write
|
81
200
|
@connection.close_write
|
82
201
|
end
|
83
|
-
|
84
202
|
end
|
85
203
|
|
86
204
|
module TCPCork
|
87
|
-
|
88
205
|
# On Linux, use TCP_CORK to better control how the TCP stack
|
89
206
|
# packetizes our stream. This improves both latency and throughput.
|
90
207
|
# TCP_CORK disables Nagle's algorithm, which is ideal for sporadic
|
@@ -96,11 +213,11 @@ module Sanford
|
|
96
213
|
if RUBY_PLATFORM =~ /linux/
|
97
214
|
# 3 == TCP_CORK
|
98
215
|
def self.apply(socket)
|
99
|
-
socket.setsockopt(Socket::IPPROTO_TCP, 3, true)
|
216
|
+
socket.setsockopt(::Socket::IPPROTO_TCP, 3, true)
|
100
217
|
end
|
101
218
|
|
102
219
|
def self.remove(socket)
|
103
|
-
socket.setsockopt(Socket::IPPROTO_TCP, 3, false)
|
220
|
+
socket.setsockopt(::Socket::IPPROTO_TCP, 3, false)
|
104
221
|
end
|
105
222
|
else
|
106
223
|
def self.apply(socket)
|
@@ -109,9 +226,61 @@ module Sanford
|
|
109
226
|
def self.remove(socket)
|
110
227
|
end
|
111
228
|
end
|
229
|
+
end
|
230
|
+
|
231
|
+
class Configuration
|
232
|
+
include NsOptions::Proxy
|
112
233
|
|
234
|
+
option :name, String, :required => true
|
235
|
+
option :ip, String, :required => true, :default => '0.0.0.0'
|
236
|
+
option :port, Integer, :required => true
|
237
|
+
option :pid_file, Pathname
|
238
|
+
|
239
|
+
option :receives_keep_alive, NsOptions::Boolean, :default => false
|
240
|
+
|
241
|
+
option :verbose_logging, :default => true
|
242
|
+
option :logger, :default => proc{ NullLogger.new }
|
243
|
+
option :template_source, :default => proc{ NullTemplateSource.new }
|
244
|
+
|
245
|
+
attr_accessor :init_procs, :error_procs
|
246
|
+
attr_accessor :router
|
247
|
+
|
248
|
+
def initialize(values = nil)
|
249
|
+
super(values)
|
250
|
+
@init_procs, @error_procs = [], []
|
251
|
+
@router = Sanford::Router.new
|
252
|
+
@valid = nil
|
253
|
+
end
|
254
|
+
|
255
|
+
def routes
|
256
|
+
@router.routes
|
257
|
+
end
|
258
|
+
|
259
|
+
def to_hash
|
260
|
+
super.merge({
|
261
|
+
:error_procs => self.error_procs,
|
262
|
+
:routes => self.routes,
|
263
|
+
:template_source => self.template_source
|
264
|
+
})
|
265
|
+
end
|
266
|
+
|
267
|
+
def valid?
|
268
|
+
!!@valid
|
269
|
+
end
|
270
|
+
|
271
|
+
def validate!
|
272
|
+
return @valid if !@valid.nil?
|
273
|
+
self.init_procs.each(&:call)
|
274
|
+
if !self.required_set?
|
275
|
+
raise InvalidError, "a name, ip and port must be configured"
|
276
|
+
end
|
277
|
+
self.routes.each(&:validate!)
|
278
|
+
@valid = true
|
279
|
+
end
|
113
280
|
end
|
114
281
|
|
282
|
+
InvalidError = Class.new(RuntimeError)
|
283
|
+
|
115
284
|
end
|
116
285
|
|
117
286
|
end
|