sanford 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/Gemfile +1 -1
  2. data/README.md +41 -56
  3. data/Rakefile +0 -1
  4. data/bench/client.rb +8 -3
  5. data/bench/{services.rb → config.sanford} +11 -6
  6. data/bench/{runner.rb → report.rb} +2 -2
  7. data/bench/report.txt +32 -32
  8. data/lib/sanford/cli.rb +42 -28
  9. data/lib/sanford/config_file.rb +79 -0
  10. data/lib/sanford/{worker.rb → connection_handler.rb} +28 -20
  11. data/lib/sanford/error_handler.rb +7 -7
  12. data/lib/sanford/pid_file.rb +42 -0
  13. data/lib/sanford/process.rb +136 -0
  14. data/lib/sanford/process_signal.rb +20 -0
  15. data/lib/sanford/route.rb +48 -0
  16. data/lib/sanford/router.rb +36 -0
  17. data/lib/sanford/runner.rb +30 -58
  18. data/lib/sanford/sanford_runner.rb +19 -9
  19. data/lib/sanford/server.rb +211 -42
  20. data/lib/sanford/server_data.rb +47 -0
  21. data/lib/sanford/service_handler.rb +8 -46
  22. data/lib/sanford/template_source.rb +19 -2
  23. data/lib/sanford/test_runner.rb +27 -28
  24. data/lib/sanford/version.rb +1 -1
  25. data/lib/sanford.rb +1 -23
  26. data/sanford.gemspec +4 -5
  27. data/test/helper.rb +3 -20
  28. data/test/support/app_server.rb +142 -0
  29. data/test/support/config.sanford +7 -0
  30. data/test/support/config_invalid_run.sanford +3 -0
  31. data/test/support/config_no_run.sanford +0 -0
  32. data/test/support/fake_server_connection.rb +58 -0
  33. data/test/support/pid_file_spy.rb +19 -0
  34. data/test/support/template.erb +1 -0
  35. data/test/system/server_tests.rb +378 -0
  36. data/test/system/service_handler_tests.rb +224 -0
  37. data/test/unit/cli_tests.rb +187 -0
  38. data/test/unit/config_file_tests.rb +59 -0
  39. data/test/unit/connection_handler_tests.rb +254 -0
  40. data/test/unit/error_handler_tests.rb +30 -35
  41. data/test/unit/pid_file_tests.rb +70 -0
  42. data/test/unit/process_signal_tests.rb +61 -0
  43. data/test/unit/process_tests.rb +428 -0
  44. data/test/unit/route_tests.rb +92 -0
  45. data/test/unit/router_tests.rb +65 -0
  46. data/test/unit/runner_tests.rb +61 -15
  47. data/test/unit/sanford_runner_tests.rb +162 -28
  48. data/test/unit/sanford_tests.rb +0 -8
  49. data/test/unit/server_data_tests.rb +87 -0
  50. data/test/unit/server_tests.rb +502 -21
  51. data/test/unit/service_handler_tests.rb +114 -219
  52. data/test/unit/template_engine_tests.rb +1 -1
  53. data/test/unit/template_source_tests.rb +56 -16
  54. data/test/unit/test_runner_tests.rb +206 -0
  55. metadata +67 -67
  56. data/bench/tasks.rb +0 -41
  57. data/lib/sanford/config.rb +0 -28
  58. data/lib/sanford/host.rb +0 -129
  59. data/lib/sanford/host_data.rb +0 -65
  60. data/lib/sanford/hosts.rb +0 -38
  61. data/lib/sanford/manager.rb +0 -275
  62. data/test/support/fake_connection.rb +0 -36
  63. data/test/support/helpers.rb +0 -17
  64. data/test/support/service_handlers.rb +0 -154
  65. data/test/support/services.rb +0 -123
  66. data/test/support/simple_client.rb +0 -62
  67. data/test/system/request_handling_tests.rb +0 -306
  68. data/test/unit/config_tests.rb +0 -56
  69. data/test/unit/host_data_tests.rb +0 -71
  70. data/test/unit/host_tests.rb +0 -141
  71. data/test/unit/hosts_tests.rb +0 -50
  72. data/test/unit/manager_tests.rb +0 -195
  73. 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
@@ -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
- module Runner
7
+ class Runner
7
8
 
8
9
  ResponseArgs = Struct.new(:status, :data)
9
10
 
10
- def self.included(klass)
11
- klass.class_eval do
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
- def run!
40
- raise NotImplementedError
41
- end
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
- def build_response(args)
61
- Sanford::Protocol::Response.new(args.status, args.data) if args
62
- end
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
- module ClassMethods
36
+ private
67
37
 
68
- def run(handler_class, params = nil, logger = nil)
69
- request = Sanford::Protocol::Request.new('name', params || {})
70
- self.new(handler_class, request, logger).run
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
- include Sanford::Runner
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
- run_callbacks self.handler_class.before_callbacks
13
- self.handler.init
14
- response_args = self.handler.run
15
- run_callbacks self.handler_class.after_callbacks
16
- response_args
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
@@ -1,62 +1,181 @@
1
1
  require 'dat-tcp'
2
- require 'ostruct'
2
+ require 'ns-options'
3
+ require 'ns-options/boolean'
3
4
  require 'sanford-protocol'
4
-
5
- require 'sanford/host_data'
6
- require 'sanford/worker'
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
- class Server
11
- include DatTCP::Server
12
-
13
- attr_reader :sanford_host, :sanford_host_data, :sanford_host_options
14
+ module Server
14
15
 
15
- def initialize(host, options = nil)
16
- options ||= {}
17
- @sanford_host = host
18
- @sanford_host_options = {
19
- :receives_keep_alive => options.delete(:keep_alive),
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
- # TCP_NODELAY is set to disable buffering. In the case of Sanford
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
- def configure_tcp_server(tcp_server)
31
- tcp_server.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
32
- end
25
+ attr_reader :server_data, :dat_tcp_server
33
26
 
34
- def on_run
35
- @sanford_host_data = Sanford::HostData.new(@sanford_host, @sanford_host_options)
36
- end
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
- # `serve!` can be called at the same time by multiple threads. Thus we
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
- def serve!(socket)
44
- connection = Connection.new(socket)
45
- if !self.keep_alive_connection?(connection)
46
- Sanford::Worker.new(@sanford_host_data, connection).run
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
- protected
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