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.
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