sanford 0.14.0 → 0.15.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.
@@ -7,10 +7,6 @@ module Sanford
7
7
 
8
8
  class ConnectionHandler
9
9
 
10
- ProcessedService = Struct.new(
11
- :request, :handler_class, :response, :exception, :time_taken
12
- )
13
-
14
10
  attr_reader :server_data, :connection
15
11
  attr_reader :logger
16
12
 
@@ -38,47 +34,52 @@ module Sanford
38
34
  protected
39
35
 
40
36
  def run!
41
- service = ProcessedService.new
37
+ processed_service = ProcessedService.new
42
38
  begin
43
39
  request = Sanford::Protocol::Request.parse(@connection.read_data)
44
40
  self.log_request(request)
45
- service.request = request
41
+ processed_service.request = request
46
42
 
47
43
  route = @server_data.route_for(request.name)
48
44
  self.log_handler_class(route.handler_class)
49
- service.handler_class = route.handler_class
45
+ processed_service.handler_class = route.handler_class
50
46
 
51
47
  response = route.run(request, @server_data)
52
- service.response = response
48
+ processed_service.response = response
53
49
  rescue StandardError => exception
54
- self.handle_exception(service, exception, @server_data)
50
+ self.handle_exception(exception, @server_data, processed_service)
55
51
  ensure
56
- self.write_response(service)
52
+ self.write_response(processed_service)
57
53
  end
58
- service
54
+ processed_service
59
55
  end
60
56
 
61
- def write_response(service)
57
+ def write_response(processed_service)
62
58
  begin
63
- @connection.write_data service.response.to_hash
59
+ @connection.write_data processed_service.response.to_hash
64
60
  rescue StandardError => exception
65
- service = self.handle_exception(service, exception)
66
- @connection.write_data service.response.to_hash
61
+ processed_service = self.handle_exception(
62
+ exception,
63
+ @server_data,
64
+ processed_service
65
+ )
66
+ @connection.write_data processed_service.response.to_hash
67
67
  end
68
68
  @connection.close_write
69
- service
69
+ processed_service
70
70
  end
71
71
 
72
- def handle_exception(service, exception, server_data = nil)
73
- error_handler = Sanford::ErrorHandler.new(
74
- exception,
75
- server_data,
76
- service.request
77
- )
78
- service.response = error_handler.run
79
- service.exception = error_handler.exception
80
- self.log_exception(service.exception)
81
- service
72
+ def handle_exception(exception, server_data, processed_service)
73
+ error_handler = Sanford::ErrorHandler.new(exception, {
74
+ :server_data => server_data,
75
+ :request => processed_service.request,
76
+ :handler_class => processed_service.handler_class,
77
+ :response => processed_service.response
78
+ })
79
+ processed_service.response = error_handler.run
80
+ processed_service.exception = error_handler.exception
81
+ self.log_exception(processed_service.exception)
82
+ processed_service
82
83
  end
83
84
 
84
85
  def raise_if_debugging!(exception)
@@ -101,18 +102,7 @@ module Sanford
101
102
  def log_complete(processed_service)
102
103
  log_verbose "===== Completed in #{processed_service.time_taken}ms " \
103
104
  "#{processed_service.response.status} ====="
104
- summary_line_args = {
105
- 'time' => processed_service.time_taken,
106
- 'handler' => processed_service.handler_class
107
- }
108
- if processed_service.response
109
- summary_line_args['status'] = processed_service.response.code
110
- end
111
- if (request = processed_service.request)
112
- summary_line_args['service'] = request.name
113
- summary_line_args['params'] = request.params
114
- end
115
- log_summary SummaryLine.new(summary_line_args)
105
+ log_summary build_summary_line(processed_service)
116
106
  end
117
107
 
118
108
  def log_exception(exception)
@@ -129,6 +119,32 @@ module Sanford
129
119
  self.logger.summary.send(level, "[Sanford] #{message}")
130
120
  end
131
121
 
122
+ def build_summary_line(processed_service)
123
+ summary_line_args = {
124
+ 'time' => processed_service.time_taken,
125
+ 'handler' => processed_service.handler_class
126
+ }
127
+ if (request = processed_service.request)
128
+ summary_line_args['service'] = request.name
129
+ summary_line_args['params'] = request.params.to_hash
130
+ end
131
+ if (response = processed_service.response)
132
+ summary_line_args['status'] = response.code
133
+ end
134
+ if (exception = processed_service.exception)
135
+ summary_line_args['error'] = "#{exception.class}: #{exception.message}"
136
+ end
137
+ SummaryLine.new(summary_line_args)
138
+ end
139
+
140
+ module SummaryLine
141
+ KEYS = %w{time status handler service params error}.freeze
142
+
143
+ def self.new(line_attrs)
144
+ KEYS.map{ |k| "#{k}=#{line_attrs[k].inspect}" }.join(' ')
145
+ end
146
+ end
147
+
132
148
  module RoundedTime
133
149
  ROUND_PRECISION = 2
134
150
  ROUND_MODIFIER = 10 ** ROUND_PRECISION
@@ -137,12 +153,9 @@ module Sanford
137
153
  end
138
154
  end
139
155
 
140
- module SummaryLine
141
- def self.new(line_attrs)
142
- attr_keys = %w{time status handler service params}
143
- attr_keys.map{ |k| "#{k}=#{line_attrs[k].inspect}" }.join(' ')
144
- end
145
- end
156
+ ProcessedService = Struct.new(
157
+ :request, :handler_class, :response, :exception, :time_taken
158
+ )
146
159
 
147
160
  end
148
161
 
@@ -1,16 +1,15 @@
1
- require 'ostruct'
2
1
  require 'sanford-protocol'
3
2
 
4
3
  module Sanford
5
4
 
6
5
  class ErrorHandler
7
6
 
8
- attr_reader :exception, :server_data, :request
9
- attr_reader :error_procs
7
+ attr_reader :exception, :context, :error_procs
10
8
 
11
- def initialize(exception, server_data = nil, request = nil)
12
- @exception, @server_data, @request = exception, server_data, request
13
- @error_procs = @server_data ? @server_data.error_procs.reverse : []
9
+ def initialize(exception, context_hash)
10
+ @exception = exception
11
+ @context = ErrorContext.new(context_hash)
12
+ @error_procs = context_hash[:server_data].error_procs.reverse
14
13
  end
15
14
 
16
15
  # The exception that we are generating a response for can change in the case
@@ -24,44 +23,69 @@ module Sanford
24
23
  @error_procs.each do |error_proc|
25
24
  result = nil
26
25
  begin
27
- result = error_proc.call(@exception, @server_data, @request)
26
+ result = error_proc.call(@exception, @context)
28
27
  rescue StandardError => proc_exception
29
28
  @exception = proc_exception
30
29
  end
31
- response ||= self.response_from_proc(result)
30
+ response ||= response_from_proc(result)
32
31
  end
33
- response || self.response_from_exception(@exception)
32
+ response || response_from_exception(@exception)
34
33
  end
35
34
 
36
- protected
35
+ private
37
36
 
38
37
  def response_from_proc(result)
39
- case result
40
- when Sanford::Protocol::Response
38
+ if result.kind_of?(Sanford::Protocol::Response)
41
39
  result
42
- when Integer, Symbol
40
+ elsif result.kind_of?(Integer) || result.kind_of?(Symbol)
43
41
  build_response result
44
42
  end
45
43
  end
46
44
 
47
45
  def response_from_exception(exception)
48
- case(exception)
49
- when Sanford::Protocol::BadMessageError, Sanford::Protocol::Request::InvalidError
46
+ if exception.kind_of?(Sanford::Protocol::BadMessageError) ||
47
+ exception.kind_of?(Sanford::Protocol::Request::InvalidError)
50
48
  build_response :bad_request, :message => exception.message
51
- when Sanford::NotFoundError
49
+ elsif exception.kind_of?(Sanford::NotFoundError)
52
50
  build_response :not_found
53
- when Sanford::Protocol::TimeoutError
51
+ elsif exception.kind_of?(Sanford::Protocol::TimeoutError)
54
52
  build_response :timeout
55
- when Exception
53
+ else
56
54
  build_response :error, :message => "An unexpected error occurred."
57
55
  end
58
56
  end
59
57
 
60
58
  def build_response(status, options = nil)
61
- options = OpenStruct.new(options || {})
62
- Sanford::Protocol::Response.new([ status, options.message ], options.data)
59
+ options ||= {}
60
+ Sanford::Protocol::Response.new(
61
+ [status, options[:message]],
62
+ options[:data]
63
+ )
63
64
  end
64
65
 
65
66
  end
66
67
 
68
+ class ErrorContext
69
+ attr_reader :server_data
70
+ attr_reader :request, :handler_class, :response
71
+
72
+ def initialize(args)
73
+ @server_data = args[:server_data]
74
+ @request = args[:request]
75
+ @handler_class = args[:handler_class]
76
+ @response = args[:response]
77
+ end
78
+
79
+ def ==(other)
80
+ if other.kind_of?(self.class)
81
+ self.server_data == other.server_data &&
82
+ self.request == other.request &&
83
+ self.handler_class == other.handler_class &&
84
+ self.response == other.response
85
+ else
86
+ super
87
+ end
88
+ end
89
+ end
90
+
67
91
  end
@@ -14,20 +14,19 @@ module Sanford
14
14
  @pid_file = PIDFile.new(@server.pid_file)
15
15
  @restart_cmd = RestartCmd.new
16
16
 
17
- @server_ip = default_if_blank(ENV['SANFORD_IP'], @server.configured_ip)
18
- @server_port = default_if_blank(
19
- ENV['SANFORD_PORT'],
20
- @server.configured_port
21
- ){ |v| v.to_i }
22
- @server_fd = ignore_if_blank(ENV['SANFORD_SERVER_FD']){ |v| v.to_i }
23
- @listen_args = @server_fd ? [ @server_fd ] : [ @server_ip, @server_port ]
17
+ @server_ip = @server.configured_ip
18
+ @server_port = @server.configured_port
19
+ @server_fd = if !ENV['SANFORD_SERVER_FD'].to_s.empty?
20
+ ENV['SANFORD_SERVER_FD'].to_i
21
+ end
22
+ @listen_args = @server_fd ? [@server_fd] : [@server_ip, @server_port]
24
23
 
25
24
  @name = "sanford-#{@server.name}-#{@server_ip}-#{@server_port}"
26
25
 
27
- @client_fds = (ENV['SANFORD_CLIENT_FDS'] || "").split(',').map(&:to_i)
26
+ @client_fds = ENV['SANFORD_CLIENT_FDS'].to_s.split(',').map(&:to_i)
28
27
 
29
- @daemonize = !!options[:daemonize]
30
- @skip_daemonize = !!ignore_if_blank(ENV['SANFORD_SKIP_DAEMONIZE'])
28
+ @daemonize = !!options[:daemonize]
29
+ @skip_daemonize = !ENV['SANFORD_SKIP_DAEMONIZE'].to_s.empty?
31
30
  end
32
31
 
33
32
  def run
@@ -74,15 +73,6 @@ module Sanford
74
73
  @restart_cmd.run
75
74
  end
76
75
 
77
- def default_if_blank(value, default, &block)
78
- ignore_if_blank(value, &block) || default
79
- end
80
-
81
- def ignore_if_blank(value, &block)
82
- block ||= proc{ |v| v }
83
- block.call(value) if value && !value.empty?
84
- end
85
-
86
76
  end
87
77
 
88
78
  class RestartCmd
@@ -28,7 +28,7 @@ module Sanford
28
28
  def initialize
29
29
  self.class.configuration.validate!
30
30
  @server_data = ServerData.new(self.class.configuration.to_hash)
31
- @dat_tcp_server = DatTCP::Server.new{ |socket| serve(socket) }
31
+ @dat_tcp_server = build_dat_tcp_server
32
32
  rescue InvalidError => exception
33
33
  exception.set_backtrace(caller)
34
34
  raise exception
@@ -83,6 +83,8 @@ module Sanford
83
83
  @dat_tcp_server.listen(*args) do |server_socket|
84
84
  configure_tcp_server(server_socket)
85
85
  end
86
+ @server_data.ip = self.ip
87
+ @server_data.port = self.port
86
88
  end
87
89
 
88
90
  def start(*args)
@@ -107,6 +109,18 @@ module Sanford
107
109
 
108
110
  private
109
111
 
112
+ def build_dat_tcp_server
113
+ s = DatTCP::Server.new{ |socket| serve(socket) }
114
+
115
+ # add any configured callbacks
116
+ self.server_data.worker_start_procs.each{ |p| s.on_worker_start(&p) }
117
+ self.server_data.worker_shutdown_procs.each{ |p| s.on_worker_shutdown(&p) }
118
+ self.server_data.worker_sleep_procs.each{ |p| s.on_worker_sleep(&p) }
119
+ self.server_data.worker_wakeup_procs.each{ |p| s.on_worker_wakeup(&p) }
120
+
121
+ s
122
+ end
123
+
110
124
  def serve(socket)
111
125
  connection = Connection.new(socket)
112
126
  if !keep_alive_connection?(connection)
@@ -171,6 +185,22 @@ module Sanford
171
185
  self.configuration.error_procs << block
172
186
  end
173
187
 
188
+ def on_worker_start(&block)
189
+ self.configuration.worker_start_procs << block
190
+ end
191
+
192
+ def on_worker_shutdown(&block)
193
+ self.configuration.worker_shutdown_procs << block
194
+ end
195
+
196
+ def on_worker_sleep(&block)
197
+ self.configuration.worker_sleep_procs << block
198
+ end
199
+
200
+ def on_worker_wakeup(&block)
201
+ self.configuration.worker_wakeup_procs << block
202
+ end
203
+
174
204
  def router(value = nil, &block)
175
205
  self.configuration.router = value if !value.nil?
176
206
  self.configuration.router.instance_eval(&block) if block
@@ -244,7 +274,7 @@ module Sanford
244
274
  include NsOptions::Proxy
245
275
 
246
276
  option :name, String, :required => true
247
- option :ip, String, :required => true, :default => '0.0.0.0'
277
+ option :ip, String, :required => true
248
278
  option :port, Integer, :required => true
249
279
  option :pid_file, Pathname
250
280
 
@@ -256,12 +286,18 @@ module Sanford
256
286
 
257
287
  attr_accessor :init_procs, :error_procs
258
288
  attr_accessor :router
289
+ attr_reader :worker_start_procs, :worker_shutdown_procs
290
+ attr_reader :worker_sleep_procs, :worker_wakeup_procs
259
291
 
260
292
  def initialize(values = nil)
261
293
  super(values)
294
+ self.ip = !(v = ENV['SANFORD_IP'].to_s).empty? ? v : '0.0.0.0'
295
+ self.port = !(v = ENV['SANFORD_PORT'].to_s).empty? ? v : nil
262
296
  @init_procs, @error_procs = [], []
297
+ @worker_start_procs, @worker_shutdown_procs = [], []
298
+ @worker_sleep_procs, @worker_wakeup_procs = [], []
263
299
  @router = Sanford::Router.new
264
- @valid = nil
300
+ @valid = nil
265
301
  end
266
302
 
267
303
  def routes
@@ -270,10 +306,14 @@ module Sanford
270
306
 
271
307
  def to_hash
272
308
  super.merge({
273
- :init_procs => self.init_procs,
274
- :error_procs => self.error_procs,
275
- :router => self.router,
276
- :routes => self.routes
309
+ :init_procs => self.init_procs,
310
+ :error_procs => self.error_procs,
311
+ :worker_start_procs => self.worker_start_procs,
312
+ :worker_shutdown_procs => self.worker_shutdown_procs,
313
+ :worker_sleep_procs => self.worker_sleep_procs,
314
+ :worker_wakeup_procs => self.worker_wakeup_procs,
315
+ :router => self.router,
316
+ :routes => self.routes
277
317
  })
278
318
  end
279
319
 
@@ -8,12 +8,14 @@ module Sanford
8
8
  # NsOptions overhead when reading them while handling a request.
9
9
 
10
10
  attr_reader :name
11
- attr_reader :ip, :port
12
11
  attr_reader :pid_file
13
12
  attr_reader :receives_keep_alive
14
13
  attr_reader :verbose_logging, :logger, :template_source
15
14
  attr_reader :init_procs, :error_procs
15
+ attr_reader :worker_start_procs, :worker_shutdown_procs
16
+ attr_reader :worker_sleep_procs, :worker_wakeup_procs
16
17
  attr_reader :router, :routes
18
+ attr_accessor :ip, :port
17
19
 
18
20
  def initialize(args = nil)
19
21
  args ||= {}
@@ -31,6 +33,11 @@ module Sanford
31
33
  @init_procs = args[:init_procs] || []
32
34
  @error_procs = args[:error_procs] || []
33
35
 
36
+ @worker_start_procs = args[:worker_start_procs]
37
+ @worker_shutdown_procs = args[:worker_shutdown_procs]
38
+ @worker_sleep_procs = args[:worker_sleep_procs]
39
+ @worker_wakeup_procs = args[:worker_wakeup_procs]
40
+
34
41
  @router = args[:router]
35
42
  @routes = build_routes(args[:routes] || [])
36
43
  end
@@ -1,3 +1,3 @@
1
1
  module Sanford
2
- VERSION = "0.14.0"
2
+ VERSION = "0.15.0"
3
3
  end
data/sanford.gemspec CHANGED
@@ -18,9 +18,9 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
20
 
21
- gem.add_dependency("dat-tcp", ["~> 0.6"])
21
+ gem.add_dependency("dat-tcp", ["~> 0.7"])
22
22
  gem.add_dependency("ns-options", ["~> 1.1"])
23
23
  gem.add_dependency("sanford-protocol", ["~> 0.11"])
24
24
 
25
- gem.add_development_dependency("assert", ["~> 2.14"])
25
+ gem.add_development_dependency("assert", ["~> 2.15"])
26
26
  end
@@ -21,7 +21,7 @@ class AppServer
21
21
  include Sanford::Server
22
22
 
23
23
  name 'app'
24
- ip 'localhost'
24
+ ip '127.0.0.1'
25
25
  port 12000
26
26
 
27
27
  receives_keep_alive true
@@ -45,9 +45,10 @@ class AppServer
45
45
  template_source s
46
46
  end
47
47
 
48
- error do |exception, server_data, request|
49
- if request && request.name == 'custom_error'
50
- data = "The server on #{server_data.ip}:#{server_data.port} " \
48
+ error do |exception, context|
49
+ if context.request && context.request.name == 'custom_error'
50
+ data = "The server on " \
51
+ "#{context.server_data.ip}:#{context.server_data.port} " \
51
52
  "threw a #{exception.class}."
52
53
  Sanford::Protocol::Response.new(200, data)
53
54
  end
@@ -3,4 +3,12 @@ require 'assert/factory'
3
3
  module Factory
4
4
  extend Assert::Factory
5
5
 
6
+ def self.exception(klass = nil, message = nil)
7
+ klass ||= StandardError
8
+ message ||= Factory.text
9
+ exception = nil
10
+ begin; raise(klass, message); rescue StandardError => exception; end
11
+ exception
12
+ end
13
+
6
14
  end
@@ -89,12 +89,11 @@ class Sanford::ConnectionHandler
89
89
  setup do
90
90
  Assert.stub(@route, :run){ raise @exception }
91
91
 
92
- error_handler = Sanford::ErrorHandler.new(
93
- @exception,
94
- @server_data,
95
- @request
96
- )
97
- @expected_response = error_handler.run
92
+ error_handler = Sanford::ErrorHandler.new(@exception, {
93
+ :server_data => @server_data,
94
+ :request => @request
95
+ })
96
+ @expected_response = error_handler.run
98
97
  @expected_exception = error_handler.exception
99
98
 
100
99
  @processed_service = @connection_handler.run
@@ -117,16 +116,13 @@ class Sanford::ConnectionHandler
117
116
  class RunWithExceptionWhileWritingTests < InitTests
118
117
  desc "and run with an exception thrown while writing the response"
119
118
  setup do
120
- Assert.stub(@route, :run){ @response }
121
-
122
119
  @connection.raise_on_write = true
123
120
 
124
- error_handler = Sanford::ErrorHandler.new(
125
- @connection.write_exception,
126
- @server_data,
127
- @request
128
- )
129
- @expected_response = error_handler.run
121
+ error_handler = Sanford::ErrorHandler.new(@connection.write_exception, {
122
+ :server_data => @server_data,
123
+ :request => @request
124
+ })
125
+ @expected_response = error_handler.run
130
126
  @expected_exception = error_handler.exception
131
127
 
132
128
  @processed_service = @connection_handler.run
@@ -217,12 +213,14 @@ class Sanford::ConnectionHandler
217
213
  should "have logged the service" do
218
214
  time_taken = @processed_service.time_taken
219
215
  status = @processed_service.response.status.to_i
216
+ exception_msg = "#{@exception.class}: #{@exception.message}"
220
217
  expected = "[Sanford] " \
221
218
  "time=#{time_taken} " \
222
219
  "status=#{status} " \
223
220
  "handler=#{@route.handler_class} " \
224
221
  "service=#{@request.name.inspect} " \
225
- "params=#{@request.params.inspect}"
222
+ "params=#{@request.params.inspect} " \
223
+ "error=#{exception_msg.inspect}"
226
224
  assert_equal expected, subject.info_logged.join
227
225
  end
228
226