sanford 0.15.1 → 0.16.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.
data/Gemfile CHANGED
@@ -2,7 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'rake'
6
- gem 'pry', "~> 0.9.0"
5
+ gem 'rake', "~> 10.4.0"
6
+ gem 'pry', "~> 0.9.0"
7
7
 
8
8
  gem 'bson_ext'
data/bench/config.sanford CHANGED
@@ -1,3 +1,6 @@
1
+ LOGGER = Logger.new(STDOUT)
2
+ LOGGER.datetime_format = "" # turn off the datetime in the logs
3
+
1
4
  class BenchServer
2
5
  include Sanford::Server
3
6
 
@@ -5,7 +8,7 @@ class BenchServer
5
8
  port 59284
6
9
  pid_file File.expand_path("../../tmp/bench_server.pid", __FILE__)
7
10
 
8
- logger Logger.new(STDOUT)
11
+ logger LOGGER
9
12
  verbose_logging false
10
13
 
11
14
  router do
@@ -16,9 +19,13 @@ class BenchServer
16
19
  include Sanford::ServiceHandler
17
20
 
18
21
  def run!
19
- { :string => 'test', :int => 1, :float => 2.1, :boolean => true,
20
- :hash => { :something => 'else' }, :array => [ 1, 2, 3 ],
21
- :request_number => self.request.params['request_number']
22
+ { :string => 'test',
23
+ :int => 1,
24
+ :float => 2.1,
25
+ :boolean => true,
26
+ :hash => { :something => 'else' },
27
+ :array => [1, 2, 3],
28
+ :request_number => params['request_number']
22
29
  }
23
30
  end
24
31
 
data/bench/report.rb CHANGED
@@ -81,7 +81,7 @@ module Bench
81
81
  output " #{response.status}"
82
82
  output " #{response.data.inspect}"
83
83
  end
84
- rescue Exception => exception
84
+ rescue StandardError => exception
85
85
  puts "FAILED -> #{exception.class}: #{exception.message}"
86
86
  puts exception.backtrace.join("\n")
87
87
  end
data/bench/report.txt CHANGED
@@ -2,40 +2,46 @@ Running benchmark report...
2
2
 
3
3
  Hitting "simple" service with {}, 10000 times
4
4
  ....................................................................................................
5
- Total Time: 5192.7267ms
6
- Average Time: 0.5192ms
7
- Min Time: 0.3479ms
8
- Max Time: 56.1919ms
5
+ Total Time: 7600.3550ms
6
+ Average Time: 0.7600ms
7
+ Min Time: 0.3941ms
8
+ Max Time: 38.5608ms
9
9
 
10
10
  Distribution (number of requests):
11
- 0ms: 9929
12
- 0.3ms: 6583
13
- 0.4ms: 2845
14
- 0.5ms: 289
15
- 0.6ms: 130
16
- 0.7ms: 47
17
- 0.8ms: 23
18
- 0.9ms: 12
19
- 1ms: 31
20
- 1.0ms: 10
21
- 1.1ms: 6
22
- 1.2ms: 5
23
- 1.3ms: 5
24
- 1.4ms: 1
25
- 1.5ms: 2
11
+ 0ms: 9812
12
+ 0.3ms: 15
13
+ 0.4ms: 2030
14
+ 0.5ms: 2816
15
+ 0.6ms: 3236
16
+ 0.7ms: 1250
17
+ 0.8ms: 327
18
+ 0.9ms: 138
19
+ 1ms: 134
20
+ 1.0ms: 60
21
+ 1.1ms: 24
22
+ 1.2ms: 16
23
+ 1.3ms: 11
24
+ 1.4ms: 5
25
+ 1.5ms: 6
26
+ 1.6ms: 5
27
+ 1.7ms: 4
26
28
  1.8ms: 2
27
- 2ms: 1
28
- 5ms: 1
29
+ 1.9ms: 1
30
+ 2ms: 4
31
+ 6ms: 1
29
32
  7ms: 1
30
- 16ms: 1
31
- 24ms: 1
32
- 28ms: 5
33
- 29ms: 13
34
- 30ms: 4
35
- 31ms: 2
36
- 32ms: 5
37
- 33ms: 4
38
- 34ms: 1
39
- 56ms: 1
33
+ 8ms: 1
34
+ 9ms: 1
35
+ 10ms: 1
36
+ 28ms: 1
37
+ 30ms: 5
38
+ 31ms: 14
39
+ 32ms: 9
40
+ 33ms: 7
41
+ 34ms: 4
42
+ 35ms: 2
43
+ 36ms: 1
44
+ 37ms: 1
45
+ 38ms: 1
40
46
 
41
47
  Done running benchmark report
@@ -27,7 +27,6 @@ module Sanford
27
27
  end
28
28
  processed_service.time_taken = RoundedTime.new(benchmark.real)
29
29
  self.log_complete(processed_service)
30
- self.raise_if_debugging!(processed_service.exception)
31
30
  processed_service
32
31
  end
33
32
 
@@ -82,10 +81,6 @@ module Sanford
82
81
  processed_service
83
82
  end
84
83
 
85
- def raise_if_debugging!(exception)
86
- raise exception if exception && ENV['SANFORD_DEBUG']
87
- end
88
-
89
84
  def log_received
90
85
  log_verbose "===== Received request ====="
91
86
  end
@@ -12,21 +12,25 @@ module Sanford
12
12
 
13
13
  class Runner
14
14
 
15
- ResponseArgs = Struct.new(:status, :data)
15
+ DEFAULT_STATUS_CODE = 200.freeze
16
+ DEFAULT_STATUS_MSG = nil.freeze
17
+ DEFAULT_DATA = nil.freeze
16
18
 
17
19
  attr_reader :handler_class, :handler
18
- attr_reader :request, :params, :logger, :router, :template_source
20
+ attr_reader :logger, :router, :template_source
21
+ attr_reader :request, :params
19
22
 
20
23
  def initialize(handler_class, args = nil)
21
- @handler_class = handler_class
24
+ @status_code, @status_msg, @data = nil, nil, nil
22
25
 
23
- a = args || {}
24
- @request = a[:request]
25
- @params = a[:params] || {}
26
- @logger = a[:logger] || Sanford::NullLogger.new
27
- @router = a[:router] || Sanford::Router.new
28
- @template_source = a[:template_source] || Sanford::NullTemplateSource.new
26
+ args ||= {}
27
+ @logger = args[:logger] || Sanford::NullLogger.new
28
+ @router = args[:router] || Sanford::Router.new
29
+ @template_source = args[:template_source] || Sanford::NullTemplateSource.new
30
+ @request = args[:request]
31
+ @params = args[:params] || {}
29
32
 
33
+ @handler_class = handler_class
30
34
  @handler = @handler_class.new(self)
31
35
  end
32
36
 
@@ -34,32 +38,34 @@ module Sanford
34
38
  raise NotImplementedError
35
39
  end
36
40
 
37
- def render(path, locals = nil)
38
- self.template_source.render(path, self.handler, locals || {})
41
+ def to_response
42
+ Sanford::Protocol::Response.new(
43
+ [@status_code || DEFAULT_STATUS_CODE, @status_msg || DEFAULT_STATUS_MSG],
44
+ @data.nil? ? DEFAULT_DATA : @data
45
+ )
39
46
  end
40
47
 
41
- # It's best to keep what `halt` and `catch_halt` return in the same format.
42
- # Currently this is a `ResponseArgs` object. This is so no matter how the
43
- # block returns (either by throwing or running normally), you get the same
44
- # thing kind of object.
45
-
46
- def halt(status, options = nil)
47
- options ||= {}
48
- message = options[:message] || options['message']
49
- response_status = [ status, message ]
50
- response_data = options[:data] || options['data']
51
- throw :halt, ResponseArgs.new(response_status, response_data)
48
+ def status(*args)
49
+ if !args.empty?
50
+ @status_msg = (args.pop)[:message] if args.last.kind_of?(::Hash)
51
+ @status_code = args.first if !args.empty?
52
+ end
53
+ [@status_code, @status_msg]
52
54
  end
53
55
 
54
- private
56
+ def data(value = nil)
57
+ @data = value if !value.nil?
58
+ @data
59
+ end
55
60
 
56
- def catch_halt(&block)
57
- catch(:halt){ ResponseArgs.new(*block.call) }
61
+ def halt(*args)
62
+ self.status(*args)
63
+ self.data((args.pop)[:data]) if args.last.kind_of?(::Hash)
64
+ throw :halt
58
65
  end
59
66
 
60
- def build_response(&block)
61
- args = catch_halt(&block)
62
- Sanford::Protocol::Response.new(args.status, args.data)
67
+ def render(path, locals = nil)
68
+ self.data(self.template_source.render(path, self.handler, locals || {}))
63
69
  end
64
70
 
65
71
  end
@@ -5,19 +5,12 @@ module Sanford
5
5
  class SanfordRunner < Runner
6
6
 
7
7
  def run
8
- build_response do
9
- run_callbacks self.handler_class.before_callbacks
10
- self.handler.init
11
- return_value = self.handler.run
12
- run_callbacks self.handler_class.after_callbacks
13
- return_value
8
+ catch(:halt) do
9
+ self.handler.sanford_run_callback 'before'
10
+ catch(:halt){ self.handler.sanford_init; self.handler.sanford_run }
11
+ self.handler.sanford_run_callback 'after'
14
12
  end
15
- end
16
-
17
- private
18
-
19
- def run_callbacks(callbacks)
20
- callbacks.each{ |proc| self.handler.instance_eval(&proc) }
13
+ self.to_response
21
14
  end
22
15
 
23
16
  end
@@ -1,4 +1,5 @@
1
1
  require 'dat-tcp'
2
+ require 'much-plugin'
2
3
  require 'ns-options'
3
4
  require 'ns-options/boolean'
4
5
  require 'pathname'
@@ -8,17 +9,16 @@ require 'sanford/logger'
8
9
  require 'sanford/router'
9
10
  require 'sanford/server_data'
10
11
  require 'sanford/template_source'
11
- require 'sanford/connection_handler'
12
+ require 'sanford/worker'
12
13
 
13
14
  module Sanford
14
15
 
15
16
  module Server
17
+ include MuchPlugin
16
18
 
17
- def self.included(klass)
18
- klass.class_eval do
19
- extend ClassMethods
20
- include InstanceMethods
21
- end
19
+ plugin_included do
20
+ extend ClassMethods
21
+ include InstanceMethods
22
22
  end
23
23
 
24
24
  module InstanceMethods
@@ -28,7 +28,14 @@ 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 = build_dat_tcp_server
31
+ @dat_tcp_server = DatTCP::Server.new(self.server_data.worker_class, {
32
+ :num_workers => self.server_data.num_workers,
33
+ :logger => self.server_data.dtcp_logger,
34
+ :shutdown_timeout => self.server_data.shutdown_timeout,
35
+ :worker_params => self.server_data.worker_params.merge({
36
+ :sanford_server_data => self.server_data
37
+ })
38
+ })
32
39
  rescue InvalidError => exception
33
40
  exception.set_backtrace(caller)
34
41
  raise exception
@@ -109,34 +116,10 @@ module Sanford
109
116
 
110
117
  private
111
118
 
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
-
124
- def serve(socket)
125
- connection = Connection.new(socket)
126
- if !keep_alive_connection?(connection)
127
- Sanford::ConnectionHandler.new(@server_data, connection).run
128
- end
129
- end
130
-
131
- def keep_alive_connection?(connection)
132
- @server_data.receives_keep_alive && connection.peek_data.empty?
133
- end
134
-
135
119
  # TCP_NODELAY is set to disable buffering. In the case of Sanford
136
120
  # communication, we have all the information we need to send up front and
137
121
  # are closing the connection, so it doesn't need to buffer.
138
122
  # See http://linux.die.net/man/7/tcp
139
-
140
123
  def configure_tcp_server(tcp_server)
141
124
  tcp_server.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
142
125
  end
@@ -165,6 +148,22 @@ module Sanford
165
148
  self.configuration.pid_file *args
166
149
  end
167
150
 
151
+ def worker_class(new_worker_class = nil)
152
+ self.configuration.worker_class = new_worker_class if new_worker_class
153
+ self.configuration.worker_class
154
+ end
155
+
156
+ def worker_params(new_worker_params = nil)
157
+ self.configuration.worker_params = new_worker_params if new_worker_params
158
+ self.configuration.worker_params
159
+ end
160
+
161
+ def num_workers(new_num_workers = nil)
162
+ self.configuration.num_workers = new_num_workers if new_num_workers
163
+ self.configuration.num_workers
164
+ end
165
+ alias :workers :num_workers
166
+
168
167
  def receives_keep_alive(*args)
169
168
  self.configuration.receives_keep_alive *args
170
169
  end
@@ -177,6 +176,11 @@ module Sanford
177
176
  self.configuration.logger *args
178
177
  end
179
178
 
179
+ def shutdown_timeout(new_timeout = nil)
180
+ self.configuration.shutdown_timeout = new_timeout if new_timeout
181
+ self.configuration.shutdown_timeout
182
+ end
183
+
180
184
  def init(&block)
181
185
  self.configuration.init_procs << block
182
186
  end
@@ -185,22 +189,6 @@ module Sanford
185
189
  self.configuration.error_procs << block
186
190
  end
187
191
 
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
-
204
192
  def router(value = nil, &block)
205
193
  self.configuration.router = value if !value.nil?
206
194
  self.configuration.router.instance_eval(&block) if block
@@ -213,66 +201,11 @@ module Sanford
213
201
 
214
202
  end
215
203
 
216
- class Connection
217
- DEFAULT_TIMEOUT = 1
218
-
219
- attr_reader :timeout
220
-
221
- def initialize(socket)
222
- @socket = socket
223
- @connection = Sanford::Protocol::Connection.new(@socket)
224
- @timeout = (ENV['SANFORD_TIMEOUT'] || DEFAULT_TIMEOUT).to_f
225
- end
226
-
227
- def read_data
228
- @connection.read(@timeout)
229
- end
230
-
231
- def write_data(data)
232
- TCPCork.apply(@socket)
233
- @connection.write data
234
- TCPCork.remove(@socket)
235
- end
236
-
237
- def peek_data
238
- @connection.peek(@timeout)
239
- end
240
-
241
- def close_write
242
- @connection.close_write
243
- end
244
- end
245
-
246
- module TCPCork
247
- # On Linux, use TCP_CORK to better control how the TCP stack
248
- # packetizes our stream. This improves both latency and throughput.
249
- # TCP_CORK disables Nagle's algorithm, which is ideal for sporadic
250
- # traffic (like Telnet) but is less optimal for HTTP. Sanford is similar
251
- # to HTTP, it doesn't receive sporadic packets, it has all its data
252
- # come in at once.
253
- # For more information: http://baus.net/on-tcp_cork
254
-
255
- if RUBY_PLATFORM =~ /linux/
256
- # 3 == TCP_CORK
257
- def self.apply(socket)
258
- socket.setsockopt(::Socket::IPPROTO_TCP, 3, true)
259
- end
260
-
261
- def self.remove(socket)
262
- socket.setsockopt(::Socket::IPPROTO_TCP, 3, false)
263
- end
264
- else
265
- def self.apply(socket)
266
- end
267
-
268
- def self.remove(socket)
269
- end
270
- end
271
- end
272
-
273
204
  class Configuration
274
205
  include NsOptions::Proxy
275
206
 
207
+ DEFAULT_NUM_WORKERS = 4
208
+
276
209
  option :name, String, :required => true
277
210
  option :ip, String, :required => true, :default => '0.0.0.0'
278
211
  option :port, Integer, :required => true
@@ -285,16 +218,18 @@ module Sanford
285
218
  option :template_source, :default => proc{ NullTemplateSource.new }
286
219
 
287
220
  attr_accessor :init_procs, :error_procs
221
+ attr_accessor :worker_class, :worker_params, :num_workers
222
+ attr_accessor :shutdown_timeout
288
223
  attr_accessor :router
289
- attr_reader :worker_start_procs, :worker_shutdown_procs
290
- attr_reader :worker_sleep_procs, :worker_wakeup_procs
291
224
 
292
225
  def initialize(values = nil)
293
226
  super(values)
294
227
  @init_procs, @error_procs = [], []
295
- @worker_start_procs, @worker_shutdown_procs = [], []
296
- @worker_sleep_procs, @worker_wakeup_procs = [], []
297
- @router = Sanford::Router.new
228
+ @worker_class = DefaultWorker
229
+ @worker_params = nil
230
+ @num_workers = DEFAULT_NUM_WORKERS
231
+ @shutdown_timeout = nil
232
+ @router = Sanford::Router.new
298
233
  @valid = nil
299
234
  end
300
235
 
@@ -304,14 +239,14 @@ module Sanford
304
239
 
305
240
  def to_hash
306
241
  super.merge({
307
- :init_procs => self.init_procs,
308
- :error_procs => self.error_procs,
309
- :worker_start_procs => self.worker_start_procs,
310
- :worker_shutdown_procs => self.worker_shutdown_procs,
311
- :worker_sleep_procs => self.worker_sleep_procs,
312
- :worker_wakeup_procs => self.worker_wakeup_procs,
313
- :router => self.router,
314
- :routes => self.routes
242
+ :init_procs => self.init_procs,
243
+ :error_procs => self.error_procs,
244
+ :worker_class => self.worker_class,
245
+ :worker_params => self.worker_params,
246
+ :num_workers => self.num_workers,
247
+ :shutdown_timeout => self.shutdown_timeout,
248
+ :router => self.router,
249
+ :routes => self.routes
315
250
  })
316
251
  end
317
252
 
@@ -325,11 +260,16 @@ module Sanford
325
260
  if !self.required_set?
326
261
  raise InvalidError, "a name, ip and port must be configured"
327
262
  end
263
+ if !self.worker_class.kind_of?(Class) || !self.worker_class.include?(Sanford::Worker)
264
+ raise InvalidError, "worker class must include `#{Sanford::Worker}`"
265
+ end
328
266
  self.routes.each(&:validate!)
329
267
  @valid = true
330
268
  end
331
269
  end
332
270
 
271
+ DefaultWorker = Class.new{ include Sanford::Worker }
272
+
333
273
  InvalidError = Class.new(RuntimeError)
334
274
 
335
275
  end
@@ -10,10 +10,10 @@ module Sanford
10
10
  attr_reader :name
11
11
  attr_reader :pid_file
12
12
  attr_reader :receives_keep_alive
13
- attr_reader :verbose_logging, :logger, :template_source
13
+ attr_reader :worker_class, :worker_params, :num_workers
14
+ attr_reader :debug, :logger, :dtcp_logger, :verbose_logging
15
+ attr_reader :template_source, :shutdown_timeout
14
16
  attr_reader :init_procs, :error_procs
15
- attr_reader :worker_start_procs, :worker_shutdown_procs
16
- attr_reader :worker_sleep_procs, :worker_wakeup_procs
17
17
  attr_reader :router, :routes
18
18
  attr_accessor :ip, :port
19
19
 
@@ -26,18 +26,22 @@ module Sanford
26
26
 
27
27
  @receives_keep_alive = !!args[:receives_keep_alive]
28
28
 
29
- @verbose_logging = !!args[:verbose_logging]
29
+ @worker_class = args[:worker_class]
30
+ @worker_params = args[:worker_params] || {}
31
+ @num_workers = args[:num_workers]
32
+
33
+ @debug = !ENV['SANFORD_DEBUG'].to_s.empty?
30
34
  @logger = args[:logger]
35
+ @dtcp_logger = @logger if @debug
36
+ @verbose_logging = !!args[:verbose_logging]
37
+
31
38
  @template_source = args[:template_source]
32
39
 
40
+ @shutdown_timeout = args[:shutdown_timeout]
41
+
33
42
  @init_procs = args[:init_procs] || []
34
43
  @error_procs = args[:error_procs] || []
35
44
 
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
-
41
45
  @router = args[:router]
42
46
  @routes = build_routes(args[:routes] || [])
43
47
  end