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