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