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.
- data/README.md +16 -20
- data/Rakefile +0 -2
- data/bench/report.txt +30 -32
- data/bench/runner.rb +6 -5
- data/bench/services.rb +2 -2
- data/bench/tasks.rb +23 -0
- data/bin/sanford +7 -0
- data/lib/sanford.rb +2 -1
- data/lib/sanford/cli.rb +364 -0
- data/lib/sanford/error_handler.rb +1 -0
- data/lib/sanford/host.rb +18 -13
- data/lib/sanford/host_data.rb +21 -17
- data/lib/sanford/runner.rb +6 -3
- data/lib/sanford/server.rb +64 -12
- data/lib/sanford/service_handler.rb +19 -0
- data/lib/sanford/test_runner.rb +3 -4
- data/lib/sanford/version.rb +1 -1
- data/lib/sanford/worker.rb +4 -6
- data/sanford.gemspec +1 -2
- data/test/support/fake_connection.rb +12 -3
- data/test/support/helpers.rb +11 -34
- data/test/support/service_handlers.rb +9 -0
- data/test/support/services.rb +9 -8
- data/test/support/simple_client.rb +6 -0
- data/test/system/managing_test.rb +55 -66
- data/test/system/request_handling_test.rb +248 -36
- data/test/unit/config_test.rb +1 -1
- data/test/unit/host_configuration_test.rb +2 -6
- data/test/unit/host_data_test.rb +13 -30
- data/test/unit/host_test.rb +3 -3
- data/test/unit/manager_pid_file_test.rb +45 -0
- data/test/unit/manager_test.rb +133 -8
- data/test/unit/runner_test.rb +10 -0
- data/test/unit/server_test.rb +24 -5
- data/test/unit/service_handler_test.rb +19 -0
- data/test/unit/worker_test.rb +3 -192
- metadata +22 -36
- data/lib/sanford/exceptions.rb +0 -37
- data/lib/sanford/manager.rb +0 -49
- data/lib/sanford/rake.rb +0 -42
@@ -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,
|
22
|
-
option :ip,
|
23
|
-
option :port,
|
24
|
-
option :
|
25
|
-
option :logger,
|
26
|
-
option :verbose_logging,
|
27
|
-
option :
|
28
|
-
option :
|
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
|
64
|
-
self.configuration.
|
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
|
80
|
-
self.configuration.
|
84
|
+
def init(&block)
|
85
|
+
self.configuration.init_proc = block
|
81
86
|
end
|
82
87
|
|
83
88
|
def version(name, &block)
|
data/lib/sanford/host_data.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
17
|
+
service_host.configuration.init_proc.call
|
17
18
|
|
18
|
-
|
19
|
-
|
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
|
-
@
|
26
|
-
@
|
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
|
-
|
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
|
data/lib/sanford/runner.rb
CHANGED
@@ -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
|
19
|
+
@logger = logger || Sanford.config.logger
|
17
20
|
@handler = @handler_class.new(self)
|
18
21
|
end
|
19
22
|
|
data/lib/sanford/server.rb
CHANGED
@@ -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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
21
|
-
@
|
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
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
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
|
data/lib/sanford/test_runner.rb
CHANGED
@@ -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
|
15
|
-
@request
|
16
|
-
@logger
|
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 }
|
data/lib/sanford/version.rb
CHANGED
data/lib/sanford/worker.rb
CHANGED
@@ -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}
|
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.
|
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("
|
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(
|
11
|
-
|
12
|
-
|
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)
|
data/test/support/helpers.rb
CHANGED
@@ -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(
|
5
|
+
def start_server(host, &block)
|
20
6
|
begin
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
30
|
-
|
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
|
19
|
+
module ManagerHelper
|
39
20
|
|
40
21
|
# start a Sanford server using Sanford's manager in a forked process
|
41
|
-
def
|
22
|
+
def fork_and_call(proc, &block)
|
42
23
|
pid = fork do
|
43
24
|
STDOUT.reopen('/dev/null') unless ENV['SANFORD_DEBUG']
|
44
|
-
|
45
|
-
|
25
|
+
manager = proc.call
|
26
|
+
trap("TERM"){ manager.stop }
|
46
27
|
end
|
47
|
-
sleep
|
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
|