sanford 0.15.1 → 0.16.0

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