sanford 0.4.0 → 0.6.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.
@@ -9,6 +9,7 @@ module Sanford
9
9
 
10
10
  def initialize(exception, host_data = nil, request = nil)
11
11
  @exception, @host_data, @request = exception, host_data, request
12
+ @keep_alive = @host_data ? @host_data.keep_alive : false
12
13
  @error_proc = @host_data ? @host_data.error_proc : proc{ }
13
14
  end
14
15
 
data/lib/sanford/host.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'ns-options'
2
2
  require 'pathname'
3
+ require 'singleton'
3
4
 
4
- require 'sanford/exceptions'
5
5
  require 'sanford/logger'
6
6
 
7
7
  module Sanford
@@ -18,14 +18,15 @@ module Sanford
18
18
  # effects (messing up someone's `initialize`). Thus, the `Configuration`
19
19
  # is a separate class and not on the `Host` directly.
20
20
 
21
- option :name, String
22
- option :ip, String, :default => '0.0.0.0'
23
- option :port, Integer
24
- option :pid_dir, Pathname, :default => Dir.pwd
25
- option :logger, :default => proc{ Sanford::NullLogger.new }
26
- option :verbose_logging, :default => true
27
- option :error_proc, Proc, :default => proc{ }
28
- option :setup_proc, Proc, :default => proc{ }
21
+ option :name, String
22
+ option :ip, String, :default => '0.0.0.0'
23
+ option :port, Integer
24
+ option :pid_file, Pathname
25
+ option :logger, :default => proc{ Sanford.config.logger }
26
+ option :verbose_logging, :default => true
27
+ option :receives_keep_alive, :default => false
28
+ option :error_proc, Proc, :default => proc{ }
29
+ option :init_proc, Proc, :default => proc{ }
29
30
 
30
31
  def initialize(host)
31
32
  self.name = host.class.to_s
@@ -60,8 +61,8 @@ module Sanford
60
61
  self.configuration.port *args
61
62
  end
62
63
 
63
- def pid_dir(*args)
64
- self.configuration.pid_dir *args
64
+ def pid_file(*args)
65
+ self.configuration.pid_file *args
65
66
  end
66
67
 
67
68
  def logger(*args)
@@ -72,12 +73,16 @@ module Sanford
72
73
  self.configuration.verbose_logging *args
73
74
  end
74
75
 
76
+ def receives_keep_alive(*args)
77
+ self.configuration.receives_keep_alive *args
78
+ end
79
+
75
80
  def error(&block)
76
81
  self.configuration.error_proc = block
77
82
  end
78
83
 
79
- def setup(&block)
80
- self.configuration.setup_proc = block
84
+ def init(&block)
85
+ self.configuration.init_proc = block
81
86
  end
82
87
 
83
88
  def version(name, &block)
@@ -1,4 +1,3 @@
1
- require 'sanford/exceptions'
2
1
  require 'sanford/service_handler'
3
2
 
4
3
  module Sanford
@@ -10,27 +9,23 @@ module Sanford
10
9
  # constantizing a host's handlers and merging a host's configuration with
11
10
  # optional overrides.
12
11
 
13
- attr_reader :name, :ip, :port, :pid_dir, :logger, :verbose, :error_proc
12
+ # NOTE: The `name` attribute shouldn't be removed, it is used to identify
13
+ # a `HostData`, particularly in error handlers
14
+ attr_reader :name, :logger, :verbose, :keep_alive, :error_proc
14
15
 
15
16
  def initialize(service_host, options = nil)
16
- configuration = service_host.configuration.to_hash.merge(remove_nil_values(options || {}))
17
+ service_host.configuration.init_proc.call
17
18
 
18
- @name = configuration[:name]
19
- @ip, @port = configuration[:ip], configuration[:port]
20
- @pid_dir = configuration[:pid_dir]
21
- @logger, @verbose = configuration[:logger], configuration[:verbose_logging]
22
- @error_proc = configuration[:error_proc]
23
- @setup_proc = configuration[:setup_proc]
19
+ overrides = self.remove_nil_values(options || {})
20
+ configuration = service_host.configuration.to_hash.merge(overrides)
24
21
 
25
- @versioned_services = service_host.versioned_services
26
- @handlers = {}
22
+ @name = configuration[:name]
23
+ @logger = configuration[:logger]
24
+ @verbose = configuration[:verbose_logging]
25
+ @keep_alive = configuration[:receives_keep_alive]
26
+ @error_proc = configuration[:error_proc]
27
27
 
28
- raise Sanford::InvalidHostError.new(service_host) if !self.port
29
- end
30
-
31
- def setup
32
- @setup_proc.call
33
- @handlers = @versioned_services.inject({}) do |hash, (version, services)|
28
+ @handlers = service_host.versioned_services.inject({}) do |hash, (version, services)|
34
29
  hash.merge({ version => self.constantize_services(services) })
35
30
  end
36
31
  end
@@ -59,4 +54,13 @@ module Sanford
59
54
 
60
55
  end
61
56
 
57
+ NotFoundError = Class.new(RuntimeError)
58
+
59
+ class NoHandlerClassError < RuntimeError
60
+ def initialize(handler_class_name)
61
+ super "Sanford couldn't find the service handler '#{handler_class_name}'. " \
62
+ "It doesn't exist or hasn't been required in yet."
63
+ end
64
+ end
65
+
62
66
  end
@@ -1,8 +1,6 @@
1
1
  require 'ostruct'
2
2
  require 'sanford-protocol'
3
3
 
4
- require 'sanford/logger'
5
-
6
4
  module Sanford
7
5
 
8
6
  class Runner
@@ -11,9 +9,14 @@ module Sanford
11
9
 
12
10
  attr_reader :handler_class, :request, :logger
13
11
 
12
+ def self.run(handler_class, params = nil, logger = nil)
13
+ request = Sanford::Protocol::Request.new('version', 'name', params || {})
14
+ self.new(handler_class, request, logger).run
15
+ end
16
+
14
17
  def initialize(handler_class, request, logger = nil)
15
18
  @handler_class, @request = handler_class, request
16
- @logger = logger || Sanford::NullLogger.new
19
+ @logger = logger || Sanford.config.logger
17
20
  @handler = @handler_class.new(self)
18
21
  end
19
22
 
@@ -1,4 +1,5 @@
1
1
  require 'dat-tcp'
2
+ require 'ostruct'
2
3
  require 'sanford-protocol'
3
4
 
4
5
  require 'sanford/host_data'
@@ -8,28 +9,47 @@ module Sanford
8
9
 
9
10
  class Server
10
11
  include DatTCP::Server
12
+ attr_reader :sanford_host, :sanford_host_data, :sanford_host_options
13
+
14
+ def initialize(host, options = nil)
15
+ options ||= {}
16
+ @sanford_host = host
17
+ @sanford_host_options = {
18
+ :receives_keep_alive => options.delete(:keep_alive),
19
+ :verbose_logging => options.delete(:verbose)
20
+ }
21
+ super options
22
+ end
11
23
 
12
- attr_reader :host_data
13
-
14
- def initialize(host, options = {})
15
- @host_data = host.kind_of?(Sanford::HostData) ? host : Sanford::HostData.new(host)
16
- @host_data.setup
17
- super(@host_data.ip, @host_data.port, options)
24
+ # TCP_NODELAY is set to disable buffering. In the case of Sanford
25
+ # communication, we have all the information we need to send up front and
26
+ # are closing the connection, so it doesn't need to buffer.
27
+ # See http://linux.die.net/man/7/tcp
28
+ def configure_tcp_server(tcp_server)
29
+ tcp_server.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
18
30
  end
19
31
 
20
- def name
21
- @host_data.name
32
+ def on_run
33
+ @sanford_host_data = Sanford::HostData.new(@sanford_host, @sanford_host_options)
22
34
  end
23
35
 
24
36
  # `serve` can be called at the same time by multiple threads. Thus we create
25
37
  # a new instance of the handler for every request.
38
+ # When using TCP_CORK, you "cork" the socket, handle it and then "uncork"
39
+ # it, see the `TCPCork` module for more info.
26
40
  def serve(socket)
27
- Sanford::Worker.new(@host_data, Connection.new(socket)).run
41
+ TCPCork.apply(socket)
42
+ connection = Connection.new(socket)
43
+ if !self.keep_alive_connection?(connection)
44
+ Sanford::Worker.new(@sanford_host_data, connection).run
45
+ end
46
+ TCPCork.remove(socket)
28
47
  end
29
48
 
30
- def inspect
31
- reference = '0x0%x' % (self.object_id << 1)
32
- "#<#{self.class}:#{reference} @service_host=#{@host_data.inspect}>"
49
+ protected
50
+
51
+ def keep_alive_connection?(connection)
52
+ @sanford_host_data.keep_alive && connection.peek_data.empty?
33
53
  end
34
54
 
35
55
  class Connection
@@ -49,6 +69,38 @@ module Sanford
49
69
  @connection.write data
50
70
  end
51
71
 
72
+ def peek_data
73
+ @connection.peek(@timeout)
74
+ end
75
+
76
+ end
77
+
78
+ module TCPCork
79
+
80
+ # On Linux, use TCP_CORK to better control how the TCP stack
81
+ # packetizes our stream. This improves both latency and throughput.
82
+ # TCP_CORK disables Nagle's algorithm, which is ideal for sporadic
83
+ # traffic (like Telnet) but is less optimal for HTTP. Sanford is similar
84
+ # to HTTP, it doesn't receive sporadic packets, it has all it's data
85
+ # come in at once.
86
+ # For more information: http://baus.net/on-tcp_cork
87
+ if RUBY_PLATFORM =~ /linux/
88
+ # 3 == TCP_CORK
89
+ def self.apply(socket)
90
+ socket.setsockopt(Socket::IPPROTO_TCP, 3, true)
91
+ end
92
+
93
+ def self.remove(socket)
94
+ socket.setsockopt(Socket::IPPROTO_TCP, 3, false)
95
+ end
96
+ else
97
+ def self.apply(socket)
98
+ end
99
+
100
+ def self.remove(socket)
101
+ end
102
+ end
103
+
52
104
  end
53
105
 
54
106
  end
@@ -1,4 +1,5 @@
1
1
  require 'sanford-protocol'
2
+ require 'sanford/runner'
2
3
 
3
4
  module Sanford
4
5
 
@@ -14,6 +15,12 @@ module Sanford
14
15
  false
15
16
  end
16
17
 
18
+ def self.included(klass)
19
+ klass.class_eval do
20
+ extend ClassMethods
21
+ end
22
+ end
23
+
17
24
  def initialize(runner)
18
25
  @sanford_runner = runner
19
26
  end
@@ -59,6 +66,10 @@ module Sanford
59
66
 
60
67
  # Helpers
61
68
 
69
+ def run_handler(handler_class, params = nil)
70
+ handler_class.run(params || {}, self.logger)
71
+ end
72
+
62
73
  def halt(*args)
63
74
  @sanford_runner.halt(*args)
64
75
  end
@@ -79,6 +90,14 @@ module Sanford
79
90
  self.send(callback.to_s)
80
91
  end
81
92
 
93
+ module ClassMethods
94
+
95
+ def run(params = nil, logger = nil)
96
+ Sanford::Runner.run(self, params || {}, logger)
97
+ end
98
+
99
+ end
100
+
82
101
  end
83
102
 
84
103
  end
@@ -1,6 +1,5 @@
1
1
  require 'sanford-protocol'
2
2
 
3
- require 'sanford/logger'
4
3
  require 'sanford/runner'
5
4
 
6
5
  module Sanford
@@ -11,9 +10,9 @@ module Sanford
11
10
  attr_reader :handler, :response, :request, :logger
12
11
 
13
12
  def initialize(handler_class, params = {}, logger = nil)
14
- @handler_class = handler_class
15
- @request = params.kind_of?(Sanford::Protocol::Request) ? params : test_request(params)
16
- @logger = logger || Sanford::NullLogger.new
13
+ @handler_class = handler_class
14
+ @request = params.kind_of?(Sanford::Protocol::Request) ? params : test_request(params)
15
+ @logger = logger || Sanford.config.logger
17
16
 
18
17
  @handler = @handler_class.new(self)
19
18
  @response = build_response catch(:halt){ @handler.init; nil }
@@ -1,3 +1,3 @@
1
1
  module Sanford
2
- VERSION = "0.4.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -77,7 +77,7 @@ module Sanford
77
77
  end
78
78
 
79
79
  def log_received
80
- self.logger.verbose.info("Received request")
80
+ self.logger.verbose.info("===== Received request =====")
81
81
  end
82
82
 
83
83
  def log_request(request)
@@ -91,8 +91,8 @@ module Sanford
91
91
  end
92
92
 
93
93
  def log_complete(processed_service)
94
- self.logger.verbose.info "Completed in #{processed_service.time_taken}ms " \
95
- "#{processed_service.response.status}\n"
94
+ self.logger.verbose.info "===== Completed in #{processed_service.time_taken}ms " \
95
+ "#{processed_service.response.status} ====="
96
96
  self.logger.summary.info self.summary_line(processed_service).to_s
97
97
  end
98
98
 
@@ -109,7 +109,7 @@ module Sanford
109
109
  line.add 'params', request.params
110
110
  end
111
111
  line.add 'handler', processed_service.handler_class
112
- line.add 'status', processed_service.response.status.code if processed_service.response
112
+ line.add 'status', processed_service.response.code if processed_service.response
113
113
  line.add 'duration', processed_service.time_taken
114
114
  end
115
115
  end
@@ -130,8 +130,6 @@ module Sanford
130
130
  @hash[key] = value.inspect if value
131
131
  end
132
132
 
133
- # change the key's order in the array to change the order to change the
134
- # order they appear in when logged
135
133
  def to_s
136
134
  [ 'version', 'service', 'handler', 'status', 'duration', 'params' ].map do |key|
137
135
  "#{key}=#{@hash[key]}" if @hash[key]
data/sanford.gemspec CHANGED
@@ -17,8 +17,7 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
19
 
20
- gem.add_dependency("daemons", ["~>1.1"])
21
- gem.add_dependency("dat-tcp", ["~>0.1"])
20
+ gem.add_dependency("dat-tcp", ["~>0.2"])
22
21
  gem.add_dependency("ns-options", ["~>1.0"])
23
22
  gem.add_dependency("sanford-protocol", ["~>0.5"])
24
23
 
@@ -7,9 +7,18 @@ class FakeConnection
7
7
  self.new(request.to_hash, raise_on_write)
8
8
  end
9
9
 
10
- def initialize(read_data, raise_on_write = false)
11
- @raise_on_write = raise_on_write
12
- @read_data = read_data
10
+ def initialize(*args)
11
+ if args.first.kind_of?(Sanford::Protocol::Connection)
12
+ protocol_connection = args.first
13
+ @read_data = proc{ protocol_connection.read }
14
+ @write_data = proc{|data| protocol_connection.write(data) }
15
+ else
16
+ @read_data, @raise_on_write = args
17
+ end
18
+ end
19
+
20
+ def read_data
21
+ @read_data.kind_of?(Proc) ? @read_data.call : @read_data
13
22
  end
14
23
 
15
24
  def write_data(data)
@@ -1,50 +1,31 @@
1
1
  module Test
2
2
 
3
- module Environment
4
-
5
- def self.store_and_clear_hosts
6
- @previous_hosts = Sanford.hosts.instance_variable_get("@set").dup
7
- Sanford.hosts.clear
8
- end
9
-
10
- def self.restore_hosts
11
- Sanford.instance_variable_set("@hosts", Sanford::Hosts.new(@previous_hosts))
12
- @previous_hosts = nil
13
- end
14
-
15
- end
16
-
17
3
  module ForkServerHelper
18
4
 
19
- def start_server(server, &block)
5
+ def start_server(host, &block)
20
6
  begin
21
- pid = fork do
22
- trap("TERM"){ server.stop }
23
- server.start
24
- server.join_thread
25
- end
26
- sleep 0.3 # Give time for the socket to start listening.
7
+ server = Sanford::Server.new(host, { :ready_timeout => 0.1 })
8
+ server.listen(host.ip, host.port)
9
+ thread = server.run
27
10
  yield
28
11
  ensure
29
- if pid
30
- Process.kill("TERM", pid)
31
- Process.wait(pid)
32
- end
12
+ server.halt if server
13
+ thread.join if thread
33
14
  end
34
15
  end
35
16
 
36
17
  end
37
18
 
38
- module ForkManagerHelper
19
+ module ManagerHelper
39
20
 
40
21
  # start a Sanford server using Sanford's manager in a forked process
41
- def call_sanford_manager(*args, &block)
22
+ def fork_and_call(proc, &block)
42
23
  pid = fork do
43
24
  STDOUT.reopen('/dev/null') unless ENV['SANFORD_DEBUG']
44
- trap("TERM"){ exit }
45
- Sanford::Manager.call(*args)
25
+ manager = proc.call
26
+ trap("TERM"){ manager.stop }
46
27
  end
47
- sleep 1.5 # give time for the command to run
28
+ sleep 0.3 # give time for the command to run
48
29
  yield
49
30
  ensure
50
31
  if pid
@@ -59,10 +40,6 @@ module Test
59
40
  socket.close rescue false
60
41
  end
61
42
 
62
- def expected_pid_file(host, ip, port)
63
- host.pid_dir.join("#{ip}_#{port}_#{host}.pid")
64
- end
65
-
66
43
  end
67
44
 
68
45
  end