sanford 0.10.1 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/README.md +41 -56
- data/Rakefile +0 -1
- data/bench/client.rb +8 -3
- data/bench/{services.rb → config.sanford} +11 -6
- data/bench/{runner.rb → report.rb} +2 -2
- data/bench/report.txt +32 -32
- data/lib/sanford/cli.rb +42 -28
- data/lib/sanford/config_file.rb +79 -0
- data/lib/sanford/{worker.rb → connection_handler.rb} +28 -20
- data/lib/sanford/error_handler.rb +7 -7
- data/lib/sanford/pid_file.rb +42 -0
- data/lib/sanford/process.rb +136 -0
- data/lib/sanford/process_signal.rb +20 -0
- data/lib/sanford/route.rb +48 -0
- data/lib/sanford/router.rb +36 -0
- data/lib/sanford/runner.rb +30 -58
- data/lib/sanford/sanford_runner.rb +19 -9
- data/lib/sanford/server.rb +211 -42
- data/lib/sanford/server_data.rb +47 -0
- data/lib/sanford/service_handler.rb +8 -46
- data/lib/sanford/template_source.rb +19 -2
- data/lib/sanford/test_runner.rb +27 -28
- data/lib/sanford/version.rb +1 -1
- data/lib/sanford.rb +1 -23
- data/sanford.gemspec +4 -5
- data/test/helper.rb +3 -20
- data/test/support/app_server.rb +142 -0
- data/test/support/config.sanford +7 -0
- data/test/support/config_invalid_run.sanford +3 -0
- data/test/support/config_no_run.sanford +0 -0
- data/test/support/fake_server_connection.rb +58 -0
- data/test/support/pid_file_spy.rb +19 -0
- data/test/support/template.erb +1 -0
- data/test/system/server_tests.rb +378 -0
- data/test/system/service_handler_tests.rb +224 -0
- data/test/unit/cli_tests.rb +187 -0
- data/test/unit/config_file_tests.rb +59 -0
- data/test/unit/connection_handler_tests.rb +254 -0
- data/test/unit/error_handler_tests.rb +30 -35
- data/test/unit/pid_file_tests.rb +70 -0
- data/test/unit/process_signal_tests.rb +61 -0
- data/test/unit/process_tests.rb +428 -0
- data/test/unit/route_tests.rb +92 -0
- data/test/unit/router_tests.rb +65 -0
- data/test/unit/runner_tests.rb +61 -15
- data/test/unit/sanford_runner_tests.rb +162 -28
- data/test/unit/sanford_tests.rb +0 -8
- data/test/unit/server_data_tests.rb +87 -0
- data/test/unit/server_tests.rb +502 -21
- data/test/unit/service_handler_tests.rb +114 -219
- data/test/unit/template_engine_tests.rb +1 -1
- data/test/unit/template_source_tests.rb +56 -16
- data/test/unit/test_runner_tests.rb +206 -0
- metadata +67 -67
- data/bench/tasks.rb +0 -41
- data/lib/sanford/config.rb +0 -28
- data/lib/sanford/host.rb +0 -129
- data/lib/sanford/host_data.rb +0 -65
- data/lib/sanford/hosts.rb +0 -38
- data/lib/sanford/manager.rb +0 -275
- data/test/support/fake_connection.rb +0 -36
- data/test/support/helpers.rb +0 -17
- data/test/support/service_handlers.rb +0 -154
- data/test/support/services.rb +0 -123
- data/test/support/simple_client.rb +0 -62
- data/test/system/request_handling_tests.rb +0 -306
- data/test/unit/config_tests.rb +0 -56
- data/test/unit/host_data_tests.rb +0 -71
- data/test/unit/host_tests.rb +0 -141
- data/test/unit/hosts_tests.rb +0 -50
- data/test/unit/manager_tests.rb +0 -195
- data/test/unit/worker_tests.rb +0 -24
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,28 +1,28 @@
|
|
1
1
|
# Sanford
|
2
2
|
|
3
|
-
Sanford TCP protocol server for hosting services.
|
3
|
+
Sanford TCP protocol server for hosting services. Define servers for services. Setup handlers for the services. Run the server as a daemon.
|
4
4
|
|
5
5
|
Sanford uses [Sanford::Protocol](https://github.com/redding/sanford-protocol) to communicate with clients. Check out [AndSon](https://github.com/redding/and-son) for a Ruby Sanford protocol client.
|
6
6
|
|
7
7
|
## Usage
|
8
8
|
|
9
9
|
```ruby
|
10
|
-
# define a
|
11
|
-
class
|
12
|
-
include Sanford::
|
10
|
+
# define a server
|
11
|
+
class MyServer
|
12
|
+
include Sanford::Server
|
13
13
|
|
14
14
|
port 8000
|
15
|
-
pid_file '/path/to/
|
15
|
+
pid_file '/path/to/server.pid'
|
16
16
|
|
17
17
|
# define some services
|
18
|
-
|
19
|
-
service 'get_user', '
|
18
|
+
router do
|
19
|
+
service 'get_user', 'MyServer::GetUser'
|
20
20
|
end
|
21
21
|
|
22
22
|
end
|
23
23
|
|
24
24
|
# define handlers for the services
|
25
|
-
class
|
25
|
+
class MyServer::GetUser
|
26
26
|
include Sanford::ServiceHandler
|
27
27
|
|
28
28
|
def run!
|
@@ -33,47 +33,43 @@ end
|
|
33
33
|
|
34
34
|
```
|
35
35
|
|
36
|
-
##
|
36
|
+
## Servers
|
37
37
|
|
38
|
-
To define a Sanford
|
38
|
+
To define a Sanford server, include the mixin `Sanford::Server` on a class and use the DSL to configure it. A few options can be set:
|
39
39
|
|
40
|
+
* `name` - (string) A name for the server, this is used to set the process name
|
41
|
+
and in logging.
|
40
42
|
* `ip` - (string) A hostname or IP address for the server to bind to; default: `'0.0.0.0'`.
|
41
43
|
* `port` - (integer) The port number for the server to bind to.
|
42
44
|
* `pid_file` - (string) Path to where you want the pid file to be written.
|
43
45
|
* `logger`- (logger) A logger for Sanford to use when handling requests; default: `Logger.new`.
|
44
46
|
|
45
|
-
Any values specified using the DSL act as defaults for instances of the host. You can overwritten when creating new instances:
|
46
|
-
|
47
|
-
```ruby
|
48
|
-
host = MyHost.new({ :port => 12000 })
|
49
|
-
```
|
50
|
-
|
51
47
|
## Services
|
52
48
|
|
53
49
|
```ruby
|
54
|
-
class
|
55
|
-
include Sanford::
|
50
|
+
class MyServer
|
51
|
+
include Sanford::Server
|
56
52
|
|
57
|
-
|
58
|
-
service 'get_user', '
|
53
|
+
router do
|
54
|
+
service 'get_user', 'MyServer::GetUser'
|
59
55
|
end
|
60
56
|
end
|
61
57
|
```
|
62
58
|
|
63
|
-
Services are defined on
|
59
|
+
Services are defined on servers via a router block. Each named service maps to a 'service handler' class. The service name is used to 'route' requests to handler classes.
|
64
60
|
|
65
61
|
When defining services handlers, it's typical to organize them all under a common namespace. Use `service_handler_ns` to define a default namespace for all handler classes under the version:
|
66
62
|
|
67
63
|
```ruby
|
68
|
-
class
|
69
|
-
include Sanford::
|
64
|
+
class MyServer
|
65
|
+
include Sanford::Server
|
70
66
|
|
71
|
-
|
72
|
-
service_handler_ns '
|
67
|
+
router do
|
68
|
+
service_handler_ns 'MyServer'
|
73
69
|
|
74
70
|
service 'get_user', 'GetUser'
|
75
71
|
service 'get_article', 'GetArticle'
|
76
|
-
service 'get_comments', '::
|
72
|
+
service 'get_comments', '::OtherServices::GetComments'
|
77
73
|
end
|
78
74
|
end
|
79
75
|
```
|
@@ -83,7 +79,7 @@ end
|
|
83
79
|
Define handlers by mixing in `Sanford::ServiceHandler` on a class and defining a `run!` method:
|
84
80
|
|
85
81
|
```ruby
|
86
|
-
class
|
82
|
+
class MyServer::GetUser
|
87
83
|
include Sanford::ServiceHandler
|
88
84
|
|
89
85
|
def run!
|
@@ -103,7 +99,7 @@ In addition to these, there are some helpers methods that can be used in your `r
|
|
103
99
|
* `halt`: stop processing and return response data with a status code and message
|
104
100
|
|
105
101
|
```ruby
|
106
|
-
class
|
102
|
+
class MyServer::GetUser
|
107
103
|
include Sanford::ServiceHandler
|
108
104
|
|
109
105
|
def run!
|
@@ -116,43 +112,32 @@ class MyHost::Services::GetUser
|
|
116
112
|
end
|
117
113
|
```
|
118
114
|
|
119
|
-
## Running
|
120
|
-
|
121
|
-
Sanford comes with a CLI for running hosts:
|
115
|
+
## Running Servers
|
122
116
|
|
123
|
-
|
124
|
-
* `sanford stop` - shutdown the background process running the host gracefully.
|
125
|
-
* `sanford restart` - "hot restart" the process running the host.
|
126
|
-
* `sanford run` - starts the server, but doesn't daemonize it (runs in the current ruby process). Convenient when using the server in a development environment.
|
117
|
+
To run a server, Sanford needs a config file to be defined:
|
127
118
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
sanford start # starts the first defined host
|
132
|
-
SANFORD_HOST=AnotherHost SANFORD_PORT=13001 sanford start # choose a specific host and port to run on with ENV vars
|
119
|
+
```ruby
|
120
|
+
require 'my_server'
|
121
|
+
run MyServer.new
|
133
122
|
```
|
134
123
|
|
135
|
-
|
136
|
-
|
137
|
-
Define a `name` on a Host to set a string name for your host that can be used to reference a host when using the CLI. If no name is set, Sanford will use the host's class name.
|
124
|
+
This file works like a rackup file. You require in your server and call `run`
|
125
|
+
passing an instance of the server. To use these files, Sanford comes with a CLI:
|
138
126
|
|
139
|
-
|
127
|
+
* `sanford CONFIG_FILE start` - spin up a background process running the server.
|
128
|
+
* `sanford CONFIG_FILE stop` - shutdown the background process running the server gracefully.
|
129
|
+
* `sanford CONFIG_FILE restart` - "hot restart" the process running the server.
|
130
|
+
* `sanford CONFIG_FILE run` - starts the server, but doesn't daemonize it (runs in the current ruby process). Convenient when using the server in a development environment.
|
140
131
|
|
141
|
-
|
132
|
+
Sanford will use the configuration of your server to either start a process or manage an existing one. A servers ip and port can be overwritten using environment variables:
|
142
133
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
class MyHost
|
147
|
-
include Sanford::Host
|
148
|
-
|
149
|
-
init do
|
150
|
-
require File.expand_path("../config/environment", __FILE__)
|
151
|
-
end
|
152
|
-
|
153
|
-
end
|
134
|
+
```bash
|
135
|
+
sanford my_server.sanford start # starts a process for `MyServer`
|
136
|
+
SANFORD_IP="1.2.3.4" SANFORD_PORT=13001 sanford my_server.sanford start # run the same server on a custom ip and port
|
154
137
|
```
|
155
138
|
|
139
|
+
This allows running multiple instances of the same server on ips and ports that are different than its configuration if needed.
|
140
|
+
|
156
141
|
## Contributing
|
157
142
|
|
158
143
|
1. Fork it
|
data/Rakefile
CHANGED
data/bench/client.rb
CHANGED
@@ -10,11 +10,16 @@ module Bench
|
|
10
10
|
@host, @port = [ host, port ]
|
11
11
|
end
|
12
12
|
|
13
|
-
|
13
|
+
# TCP_NODELAY is set to disable buffering. In the case of Sanford
|
14
|
+
# communication, we have all the information we need to send up front and
|
15
|
+
# are closing the connection, so it doesn't need to buffer.
|
16
|
+
# See http://linux.die.net/man/7/tcp
|
17
|
+
|
18
|
+
def call(name, params)
|
14
19
|
socket = TCPSocket.open(@host, @port)
|
15
|
-
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
|
20
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
|
16
21
|
connection = Sanford::Protocol::Connection.new(socket)
|
17
|
-
request = Sanford::Protocol::Request.new(
|
22
|
+
request = Sanford::Protocol::Request.new(name, params)
|
18
23
|
connection.write(request.to_hash)
|
19
24
|
connection.close_write
|
20
25
|
if IO.select([ socket ], nil, nil, 10)
|
@@ -1,13 +1,16 @@
|
|
1
|
-
class
|
2
|
-
include Sanford::
|
1
|
+
class BenchServer
|
2
|
+
include Sanford::Server
|
3
3
|
|
4
|
+
name 'bench'
|
4
5
|
port 59284
|
5
|
-
pid_file File.expand_path("../../tmp/
|
6
|
+
pid_file File.expand_path("../../tmp/bench_server.pid", __FILE__)
|
6
7
|
|
7
|
-
logger
|
8
|
-
verbose_logging
|
8
|
+
logger Logger.new(STDOUT)
|
9
|
+
verbose_logging false
|
9
10
|
|
10
|
-
|
11
|
+
router do
|
12
|
+
service 'simple', 'BenchServer::Simple'
|
13
|
+
end
|
11
14
|
|
12
15
|
class Simple
|
13
16
|
include Sanford::ServiceHandler
|
@@ -22,3 +25,5 @@ class BenchHost
|
|
22
25
|
end
|
23
26
|
|
24
27
|
end
|
28
|
+
|
29
|
+
run BenchServer.new
|
data/bench/report.txt
CHANGED
@@ -2,40 +2,40 @@ Running benchmark report...
|
|
2
2
|
|
3
3
|
Hitting "simple" service with {}, 10000 times
|
4
4
|
....................................................................................................
|
5
|
-
Total Time:
|
6
|
-
Average Time: 0.
|
7
|
-
Min Time: 0.
|
8
|
-
Max Time:
|
5
|
+
Total Time: 5192.7267ms
|
6
|
+
Average Time: 0.5192ms
|
7
|
+
Min Time: 0.3479ms
|
8
|
+
Max Time: 56.1919ms
|
9
9
|
|
10
10
|
Distribution (number of requests):
|
11
|
-
0ms:
|
12
|
-
0.
|
13
|
-
0.
|
14
|
-
0.
|
15
|
-
0.
|
16
|
-
0.
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
1.
|
21
|
-
1.
|
22
|
-
1.
|
23
|
-
1.
|
24
|
-
1.
|
25
|
-
1.
|
26
|
-
1.8ms:
|
27
|
-
|
28
|
-
2ms: 11
|
29
|
-
3ms: 9
|
30
|
-
4ms: 4
|
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
|
26
|
+
1.8ms: 2
|
27
|
+
2ms: 1
|
31
28
|
5ms: 1
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
29
|
+
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
|
40
40
|
|
41
41
|
Done running benchmark report
|
data/lib/sanford/cli.rb
CHANGED
@@ -1,51 +1,65 @@
|
|
1
1
|
require 'sanford'
|
2
|
+
require 'sanford/config_file'
|
3
|
+
require 'sanford/process'
|
4
|
+
require 'sanford/process_signal'
|
2
5
|
require 'sanford/version'
|
3
6
|
|
4
7
|
module Sanford
|
5
8
|
|
6
9
|
class CLI
|
7
10
|
|
8
|
-
def self.run(
|
11
|
+
def self.run(args)
|
9
12
|
self.new.run(*args)
|
10
13
|
end
|
11
14
|
|
12
|
-
def initialize
|
13
|
-
@
|
14
|
-
|
15
|
-
option :ip, "IP address to bind to", :value => String
|
16
|
-
option :port, "Port number to bind to", :value => Integer
|
17
|
-
option :config, "File defining the configured Hosts", :value => String
|
18
|
-
end
|
15
|
+
def initialize(kernel = nil)
|
16
|
+
@kernel = kernel || Kernel
|
17
|
+
@cli = CLIRB.new
|
19
18
|
end
|
20
19
|
|
21
20
|
def run(*args)
|
22
21
|
begin
|
23
|
-
|
24
|
-
@command = @cli.args.first || 'run'
|
25
|
-
Sanford.config.services_file = @cli.opts['config'] if @cli.opts['config']
|
26
|
-
Sanford.init
|
27
|
-
require 'sanford/manager'
|
28
|
-
Sanford::Manager.call(@command, @cli.opts)
|
22
|
+
run!(*args)
|
29
23
|
rescue CLIRB::HelpExit
|
30
|
-
puts help
|
24
|
+
@kernel.puts help
|
31
25
|
rescue CLIRB::VersionExit
|
32
|
-
puts Sanford::VERSION
|
33
|
-
rescue CLIRB::Error => exception
|
34
|
-
puts "#{exception.message}\n\n"
|
35
|
-
puts help
|
36
|
-
exit
|
37
|
-
rescue
|
38
|
-
|
39
|
-
puts
|
40
|
-
|
41
|
-
|
26
|
+
@kernel.puts "sanford #{Sanford::VERSION}"
|
27
|
+
rescue CLIRB::Error, Sanford::ConfigFile::InvalidError => exception
|
28
|
+
@kernel.puts "#{exception.message}\n\n"
|
29
|
+
@kernel.puts help
|
30
|
+
@kernel.exit 1
|
31
|
+
rescue StandardError => exception
|
32
|
+
@kernel.puts "#{exception.class}: #{exception.message}"
|
33
|
+
@kernel.puts exception.backtrace.join("\n")
|
34
|
+
@kernel.exit 1
|
35
|
+
end
|
36
|
+
@kernel.exit 0
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def run!(*args)
|
42
|
+
@cli.parse!(args)
|
43
|
+
command = @cli.args.pop || 'run'
|
44
|
+
config_file_path = @cli.args.pop || 'config.sanford'
|
45
|
+
server = Sanford::ConfigFile.new(config_file_path).server
|
46
|
+
case(command)
|
47
|
+
when 'run'
|
48
|
+
Sanford::Process.new(server, :daemonize => false).run
|
49
|
+
when 'start'
|
50
|
+
Sanford::Process.new(server, :daemonize => true).run
|
51
|
+
when 'stop'
|
52
|
+
Sanford::ProcessSignal.new(server, 'TERM').send
|
53
|
+
when 'restart'
|
54
|
+
Sanford::ProcessSignal.new(server, 'USR2').send
|
55
|
+
else
|
56
|
+
raise CLIRB::Error, "#{command.inspect} is not a valid command"
|
42
57
|
end
|
43
|
-
exit(0)
|
44
58
|
end
|
45
59
|
|
46
60
|
def help
|
47
|
-
"Usage: sanford
|
48
|
-
"Commands: run, start, stop, restart
|
61
|
+
"Usage: sanford [CONFIG_FILE] [COMMAND]\n" \
|
62
|
+
"Commands: run, start, stop, restart" \
|
49
63
|
"#{@cli}"
|
50
64
|
end
|
51
65
|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'sanford/server'
|
2
|
+
|
3
|
+
module Sanford
|
4
|
+
|
5
|
+
class ConfigFile
|
6
|
+
|
7
|
+
attr_reader :server
|
8
|
+
|
9
|
+
def initialize(file_path)
|
10
|
+
@file_path = build_file_path(file_path)
|
11
|
+
@server = nil
|
12
|
+
evaluate_file(@file_path)
|
13
|
+
validate!
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(server)
|
17
|
+
@server = server
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def validate!
|
23
|
+
if !@server.kind_of?(Sanford::Server)
|
24
|
+
raise NoServerError.new(@server, @file_path)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_file_path(path)
|
29
|
+
full_path = File.expand_path(path)
|
30
|
+
raise NoConfigFileError.new(full_path) unless File.exists?(full_path)
|
31
|
+
full_path
|
32
|
+
rescue NoConfigFileError
|
33
|
+
full_path_with_sanford = "#{full_path}.sanford"
|
34
|
+
raise unless File.exists?(full_path_with_sanford)
|
35
|
+
full_path_with_sanford
|
36
|
+
end
|
37
|
+
|
38
|
+
# This evaluates the file and creates a proc using it's contents. This is
|
39
|
+
# a trick borrowed from Rack. It is essentially converting a file into a
|
40
|
+
# proc and then instance eval'ing it. This has a couple benefits:
|
41
|
+
# * The obvious benefit is the file is evaluated in the context of this
|
42
|
+
# class. This allows the file to call `run`, setting the server that
|
43
|
+
# will be used.
|
44
|
+
# * The other benefit is that the file's contents behave like they were a
|
45
|
+
# proc defined by the user. Instance eval'ing the file directly, makes
|
46
|
+
# any constants (modules/classes) defined in it namespaced by the
|
47
|
+
# instance of the config (not namespaced by `Sanford::ConfigFile`,
|
48
|
+
# they are actually namespaced by an instance of this class, its like
|
49
|
+
# accessing it via `ConfigFile.new::MyServer`), which is very confusing.
|
50
|
+
# Thus, the proc is created and eval'd using the `TOPLEVEL_BINDING`,
|
51
|
+
# which defines the constants at the top-level, as would be expected.
|
52
|
+
def evaluate_file(file_path)
|
53
|
+
config_file_code = "proc{ #{File.read(file_path)} }"
|
54
|
+
config_file_proc = eval(config_file_code, TOPLEVEL_BINDING, file_path, 0)
|
55
|
+
self.instance_eval(&config_file_proc)
|
56
|
+
end
|
57
|
+
|
58
|
+
InvalidError = Class.new(StandardError)
|
59
|
+
|
60
|
+
class NoConfigFileError < InvalidError
|
61
|
+
def initialize(path)
|
62
|
+
super "A configuration file couldn't be found at: #{path.to_s.inspect}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class NoServerError < InvalidError
|
67
|
+
def initialize(server, path)
|
68
|
+
prefix = "Configuration file #{path.to_s.inspect}"
|
69
|
+
if server
|
70
|
+
super "#{prefix} called `run` without a Sanford::Server"
|
71
|
+
else
|
72
|
+
super "#{prefix} didn't call `run` with a Sanford::Server"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -1,24 +1,27 @@
|
|
1
1
|
require 'benchmark'
|
2
2
|
require 'sanford-protocol'
|
3
|
-
|
4
3
|
require 'sanford/error_handler'
|
5
4
|
require 'sanford/logger'
|
6
5
|
require 'sanford/runner'
|
7
6
|
|
8
7
|
module Sanford
|
9
8
|
|
10
|
-
class
|
9
|
+
class ConnectionHandler
|
11
10
|
|
12
|
-
ProcessedService = Struct.new(
|
11
|
+
ProcessedService = Struct.new(
|
13
12
|
:request, :handler_class, :response, :exception, :time_taken
|
14
|
-
|
13
|
+
)
|
15
14
|
|
15
|
+
attr_reader :server_data, :connection
|
16
16
|
attr_reader :logger
|
17
17
|
|
18
|
-
def initialize(
|
19
|
-
@
|
20
|
-
|
21
|
-
@logger = Sanford::Logger.new(
|
18
|
+
def initialize(server_data, connection)
|
19
|
+
@server_data = server_data
|
20
|
+
@connection = connection
|
21
|
+
@logger = Sanford::Logger.new(
|
22
|
+
@server_data.logger,
|
23
|
+
@server_data.verbose_logging
|
24
|
+
)
|
22
25
|
end
|
23
26
|
|
24
27
|
def run
|
@@ -42,14 +45,14 @@ module Sanford
|
|
42
45
|
self.log_request(request)
|
43
46
|
service.request = request
|
44
47
|
|
45
|
-
|
46
|
-
self.log_handler_class(handler_class)
|
47
|
-
service.handler_class = handler_class
|
48
|
+
route = @server_data.route_for(request.name)
|
49
|
+
self.log_handler_class(route.handler_class)
|
50
|
+
service.handler_class = route.handler_class
|
48
51
|
|
49
|
-
response =
|
52
|
+
response = route.run(request, @server_data)
|
50
53
|
service.response = response
|
51
|
-
rescue
|
52
|
-
self.handle_exception(service, exception, @
|
54
|
+
rescue StandardError => exception
|
55
|
+
self.handle_exception(service, exception, @server_data)
|
53
56
|
ensure
|
54
57
|
self.write_response(service)
|
55
58
|
end
|
@@ -59,7 +62,7 @@ module Sanford
|
|
59
62
|
def write_response(service)
|
60
63
|
begin
|
61
64
|
@connection.write_data service.response.to_hash
|
62
|
-
rescue
|
65
|
+
rescue StandardError => exception
|
63
66
|
service = self.handle_exception(service, exception)
|
64
67
|
@connection.write_data service.response.to_hash
|
65
68
|
end
|
@@ -67,8 +70,12 @@ module Sanford
|
|
67
70
|
service
|
68
71
|
end
|
69
72
|
|
70
|
-
def handle_exception(service, exception,
|
71
|
-
error_handler = Sanford::ErrorHandler.new(
|
73
|
+
def handle_exception(service, exception, server_data = nil)
|
74
|
+
error_handler = Sanford::ErrorHandler.new(
|
75
|
+
exception,
|
76
|
+
server_data,
|
77
|
+
service.request
|
78
|
+
)
|
72
79
|
service.response = error_handler.run
|
73
80
|
service.exception = error_handler.exception
|
74
81
|
self.log_exception(service.exception)
|
@@ -110,8 +117,9 @@ module Sanford
|
|
110
117
|
end
|
111
118
|
|
112
119
|
def log_exception(exception)
|
113
|
-
|
114
|
-
|
120
|
+
backtrace = exception.backtrace.join("\n")
|
121
|
+
message = "#{exception.class}: #{exception.message}\n#{backtrace}"
|
122
|
+
log_verbose(message, :error)
|
115
123
|
end
|
116
124
|
|
117
125
|
def log_verbose(message, level = :info)
|
@@ -132,7 +140,7 @@ module Sanford
|
|
132
140
|
|
133
141
|
module SummaryLine
|
134
142
|
def self.new(line_attrs)
|
135
|
-
attr_keys = %w{time status handler
|
143
|
+
attr_keys = %w{time status handler service params}
|
136
144
|
attr_keys.map{ |k| "#{k}=#{line_attrs[k].inspect}" }.join(' ')
|
137
145
|
end
|
138
146
|
end
|
@@ -5,12 +5,12 @@ module Sanford
|
|
5
5
|
|
6
6
|
class ErrorHandler
|
7
7
|
|
8
|
-
attr_reader :exception, :
|
8
|
+
attr_reader :exception, :server_data, :request
|
9
|
+
attr_reader :error_procs
|
9
10
|
|
10
|
-
def initialize(exception,
|
11
|
-
@exception, @
|
12
|
-
@
|
13
|
-
@error_procs = @host_data ? @host_data.error_procs.reverse : []
|
11
|
+
def initialize(exception, server_data = nil, request = nil)
|
12
|
+
@exception, @server_data, @request = exception, server_data, request
|
13
|
+
@error_procs = @server_data ? @server_data.error_procs.reverse : []
|
14
14
|
end
|
15
15
|
|
16
16
|
# The exception that we are generating a response for can change in the case
|
@@ -24,8 +24,8 @@ module Sanford
|
|
24
24
|
@error_procs.each do |error_proc|
|
25
25
|
result = nil
|
26
26
|
begin
|
27
|
-
result = error_proc.call(@exception, @
|
28
|
-
rescue
|
27
|
+
result = error_proc.call(@exception, @server_data, @request)
|
28
|
+
rescue StandardError => proc_exception
|
29
29
|
@exception = proc_exception
|
30
30
|
end
|
31
31
|
response ||= self.response_from_proc(result)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Sanford
|
4
|
+
|
5
|
+
class PIDFile
|
6
|
+
attr_reader :path
|
7
|
+
|
8
|
+
def initialize(path)
|
9
|
+
@path = (path || '/dev/null').to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def pid
|
13
|
+
pid = File.read(@path).strip
|
14
|
+
pid && !pid.empty? ? pid.to_i : raise('no pid in file')
|
15
|
+
rescue StandardError => exception
|
16
|
+
error = InvalidError.new("A PID couldn't be read from #{@path.inspect}")
|
17
|
+
error.set_backtrace(exception.backtrace)
|
18
|
+
raise error
|
19
|
+
end
|
20
|
+
|
21
|
+
def write
|
22
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
23
|
+
File.open(@path, 'w'){ |f| f.puts ::Process.pid }
|
24
|
+
rescue StandardError => exception
|
25
|
+
error = InvalidError.new("Can't write pid to file #{@path.inspect}")
|
26
|
+
error.set_backtrace(exception.backtrace)
|
27
|
+
raise error
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove
|
31
|
+
FileUtils.rm_f(@path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
@path
|
36
|
+
end
|
37
|
+
|
38
|
+
InvalidError = Class.new(RuntimeError)
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|