sanford 0.4.0 → 0.6.0

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