sanford 0.14.0 → 0.15.0

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