sanford 0.10.1 → 0.11.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/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
|