sanford 0.1.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/.gitignore +19 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +164 -0
- data/Rakefile +8 -0
- data/bench/client.rb +30 -0
- data/bench/report.txt +10 -0
- data/bench/runner.rb +102 -0
- data/bench/services.rb +23 -0
- data/bench/tasks.rb +18 -0
- data/lib/sanford/config.rb +33 -0
- data/lib/sanford/connection.rb +70 -0
- data/lib/sanford/exception_handler.rb +43 -0
- data/lib/sanford/exceptions.rb +37 -0
- data/lib/sanford/host.rb +162 -0
- data/lib/sanford/manager.rb +58 -0
- data/lib/sanford/rake.rb +45 -0
- data/lib/sanford/server.rb +39 -0
- data/lib/sanford/service_handler.rb +93 -0
- data/lib/sanford/version.rb +3 -0
- data/lib/sanford.rb +32 -0
- data/log/.gitkeep +0 -0
- data/sanford.gemspec +27 -0
- data/test/helper.rb +20 -0
- data/test/support/helpers.rb +72 -0
- data/test/support/service_handlers.rb +115 -0
- data/test/support/services.rb +72 -0
- data/test/support/simple_client.rb +55 -0
- data/test/system/managing_test.rb +105 -0
- data/test/system/request_handling_test.rb +202 -0
- data/test/unit/config_test.rb +54 -0
- data/test/unit/connection_test.rb +23 -0
- data/test/unit/exception_handler_test.rb +69 -0
- data/test/unit/host/version_group_test.rb +39 -0
- data/test/unit/host_test.rb +142 -0
- data/test/unit/manager_test.rb +21 -0
- data/test/unit/server_test.rb +22 -0
- data/test/unit/service_handler_test.rb +170 -0
- metadata +209 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 jcredding
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
# Sanford
|
2
|
+
|
3
|
+
Sanford: simple hosts for Sanford services. Define hosts for versioned services. Setup handlers for the services. Run the host as a daemon.
|
4
|
+
|
5
|
+
Sanford uses [Sanford::Protocol](https://github.com/redding/sanford-protocol) to communicate with clients.
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# define a host
|
11
|
+
class MyHost
|
12
|
+
include Sanford::Host
|
13
|
+
|
14
|
+
port 8000
|
15
|
+
pid_dir '/path/to/pids'
|
16
|
+
|
17
|
+
# define some services
|
18
|
+
version 'v1' do
|
19
|
+
service 'get_user', 'MyHost::V1Services::GetUser'
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
# define handlers for the services
|
25
|
+
class MyHost::V1Services::GetUser
|
26
|
+
include Sanford::ServiceHandler
|
27
|
+
|
28
|
+
def run!
|
29
|
+
# process the service call and build a response
|
30
|
+
# the return value of this method will be used as the response data
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
```
|
35
|
+
|
36
|
+
## Hosts
|
37
|
+
|
38
|
+
To define a Sanford host, include the mixin `Sanford::Host` on a class and use the DSL to configure it. A few options can be set:
|
39
|
+
|
40
|
+
* `ip` - (string) A hostname or IP address for the server to bind to; default: `'0.0.0.0'`.
|
41
|
+
* `port` - (integer) The port number for the server to bind to.
|
42
|
+
* `pid_dir` - (string) Path to the directory where you want the pid file to be written; default: `Dir.pwd`.
|
43
|
+
* `logger`- (logger) A logger for Sanford to use when handling requests; default: `Logger.new`.
|
44
|
+
|
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
|
+
## Services
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class MyHost
|
55
|
+
include Sanford::Host
|
56
|
+
|
57
|
+
version 'v1' do
|
58
|
+
service 'get_user', 'MyHost::ServicesV1::GetUser'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
Services are defined on hosts by version. Each named service maps to a 'service handler' class. The version and service name are used to 'route' requests to handler classes.
|
64
|
+
|
65
|
+
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
|
+
|
67
|
+
```ruby
|
68
|
+
class MyHost
|
69
|
+
include Sanford::Host
|
70
|
+
|
71
|
+
version 'v1' do
|
72
|
+
service_handler_ns 'MyHost::ServicesV1'
|
73
|
+
|
74
|
+
service 'get_user', 'GetUser'
|
75
|
+
service 'get_article', 'GetArticle'
|
76
|
+
service 'get_comments', '::MyHost::OtherServices::GetComments'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
## Service Handlers
|
82
|
+
|
83
|
+
Define handlers by mixing in `Sanford::ServiceHandler` on a class and defining a `run!` method:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
class MyHost::Services::GetUser
|
87
|
+
include Sanford::ServiceHandler
|
88
|
+
|
89
|
+
def run!
|
90
|
+
# process the service call and generate a response
|
91
|
+
# the return value of this method will be used as
|
92
|
+
# the response data returned to the client
|
93
|
+
end
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
This is the most basic way to define a service handler. In addition to this, the `init!` method can be overwritten. This will be called after an instance of the service handler is created. The `init!` method is intended as a hook to add constructor logic. The `initialize` method should not be overwritten.
|
98
|
+
|
99
|
+
In addition to these, there are some helpers methods that can be used in your `run!` method:
|
100
|
+
|
101
|
+
* `request`: returns the request object the host received
|
102
|
+
* `params`: returns the params payload from the request object
|
103
|
+
* `halt`: stop processing and return response data with a status code and message
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
class MyHost::Services::GetUser
|
107
|
+
include Sanford::ServiceHandler
|
108
|
+
|
109
|
+
def run!
|
110
|
+
User.find(params['user_id']).attributes
|
111
|
+
rescue NotFoundException => e
|
112
|
+
halt :not_found, :message => e.message, :data => request.params
|
113
|
+
rescue Exception => e
|
114
|
+
halt :error, :message => e.message
|
115
|
+
end
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
## Running Host Daemons
|
120
|
+
|
121
|
+
Sanford comes with rake tasks for running hosts:
|
122
|
+
|
123
|
+
* `rake sanford:start` - spin up a background process running the host daemon.
|
124
|
+
* `rake sanford:stop` - shutdown the background process running the host gracefully.
|
125
|
+
* `rake sanford:restart` - runs the stop and then the start tasks.
|
126
|
+
* `rake sanford:run` - starts the server, but don't daemonize it (runs in the current ruby process). Convenient when using the server in a development environment.
|
127
|
+
|
128
|
+
These can be installed by requiring it's rake tasks in your `Rakefile`:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
require 'sanford/rake'
|
132
|
+
```
|
133
|
+
|
134
|
+
The basic rake tasks are useful if your application only has one host defined and if you only want to run the host on a single port. In the case you have multiple hosts defined or you want to run a single host on multiple ports, use environment variables to set custom configurations.
|
135
|
+
|
136
|
+
```bash
|
137
|
+
rake sanford:start # starts the first defined host
|
138
|
+
SANFORD_HOST=AnotherHost SANFORD_PORT=13001 rake sanford:start # choose a specific host and port to run on with ENV vars
|
139
|
+
```
|
140
|
+
|
141
|
+
The rake tasks allow using environment variables for specifying which host to run the command against and for overriding the host's configuration. They recognize the following environment variables: `SANFORD_HOST`, `SANFORD_IP`, and `SANFORD_PORT`.
|
142
|
+
|
143
|
+
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 rake tasks. If no name is set, Sanford will use the host's class name.
|
144
|
+
|
145
|
+
### Loading An Application
|
146
|
+
|
147
|
+
Typically, a Sanford host is part of a larger application and parts of the application need to be setup or loaded when you start your Sanford server. The task `sanford:setup` is called before running any start, stop, or restart task; override it to hook in your application setup code:
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
# In your Rakefile
|
151
|
+
namespace :sanford do
|
152
|
+
task :setup do
|
153
|
+
require 'config/environment'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
## Contributing
|
159
|
+
|
160
|
+
1. Fork it
|
161
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
162
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
163
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
164
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bench/client.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
require 'sanford-protocol'
|
4
|
+
|
5
|
+
module Bench
|
6
|
+
|
7
|
+
class Client
|
8
|
+
|
9
|
+
def initialize(host, port)
|
10
|
+
@host, @port = [ host, port ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(version, name, params)
|
14
|
+
socket = TCPSocket.open(@host, @port)
|
15
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true) # TODO - explain
|
16
|
+
connection = Sanford::Protocol::Connection.new(socket)
|
17
|
+
request = Sanford::Protocol::Request.new(version, name, params)
|
18
|
+
connection.write(request.to_hash)
|
19
|
+
if IO.select([ socket ], nil, nil, 10)
|
20
|
+
Sanford::Protocol::Response.parse(connection.read)
|
21
|
+
else
|
22
|
+
raise "Timed out!"
|
23
|
+
end
|
24
|
+
ensure
|
25
|
+
socket.close rescue false
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/bench/report.txt
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Running benchmark report...
|
2
|
+
|
3
|
+
Hitting "simple" service with {}, 10000 times
|
4
|
+
....................................................................................................
|
5
|
+
Total Time: 15768.8473ms
|
6
|
+
Average Time: 1.5768ms
|
7
|
+
Min Time: 0.9949ms
|
8
|
+
Max Time: 102.2670ms
|
9
|
+
|
10
|
+
Done running benchmark report
|
data/bench/runner.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
Bundler.setup(:benchmark)
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
require 'bench/client'
|
5
|
+
|
6
|
+
module Bench
|
7
|
+
|
8
|
+
class Runner
|
9
|
+
# this should match up with bench/services host and port
|
10
|
+
HOST_AND_PORT = [ '127.0.0.1', 12000 ]
|
11
|
+
|
12
|
+
REQUESTS = [
|
13
|
+
[ 'v1', 'simple', {}, 10000 ]
|
14
|
+
]
|
15
|
+
|
16
|
+
TIME_MODIFIER = 10 ** 4 # 4 decimal places
|
17
|
+
|
18
|
+
def initialize(options = {})
|
19
|
+
options[:output] ||= File.expand_path("../report.txt", __FILE__)
|
20
|
+
|
21
|
+
@file = File.open(options[:output], "w")
|
22
|
+
end
|
23
|
+
|
24
|
+
def build_report
|
25
|
+
output "Running benchmark report..."
|
26
|
+
|
27
|
+
REQUESTS.each do |version, name, params, times|
|
28
|
+
self.benchmark_service(version, name, params, times, false)
|
29
|
+
end
|
30
|
+
|
31
|
+
output "Done running benchmark report"
|
32
|
+
end
|
33
|
+
|
34
|
+
def benchmark_service(version, name, params, times, show_result = false)
|
35
|
+
benchmarks = []
|
36
|
+
|
37
|
+
output "\nHitting #{name.inspect} service with #{params.inspect}, #{times} times"
|
38
|
+
[*(1..times.to_i)].each do |index|
|
39
|
+
benchmark = self.hit_service(name, version, params.merge({ :request_number => index }), show_result)
|
40
|
+
benchmarks << self.round_time(benchmark.real * 1000.to_f)
|
41
|
+
output('.', false) if ((index - 1) % 100 == 0) && !show_result
|
42
|
+
end
|
43
|
+
output("\n", false)
|
44
|
+
|
45
|
+
total_time = benchmarks.inject(0){|s, n| s + n }
|
46
|
+
data = {
|
47
|
+
:number_of_requests => times,
|
48
|
+
:total_time_taken => self.round_and_display(total_time),
|
49
|
+
:average_time_taken => self.round_and_display(total_time / benchmarks.size),
|
50
|
+
:min_time_taken => self.round_and_display(benchmarks.min),
|
51
|
+
:max_time_taken => self.round_and_display(benchmarks.max)
|
52
|
+
}
|
53
|
+
size = data.values.map(&:size).max
|
54
|
+
output "Total Time: #{data[:total_time_taken].rjust(size)}ms"
|
55
|
+
output "Average Time: #{data[:average_time_taken].rjust(size)}ms"
|
56
|
+
output "Min Time: #{data[:min_time_taken].rjust(size)}ms"
|
57
|
+
output "Max Time: #{data[:max_time_taken].rjust(size)}ms"
|
58
|
+
output "\n"
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def hit_service(version, name, params, show_result)
|
64
|
+
Benchmark.measure do
|
65
|
+
begin
|
66
|
+
client = Bench::Client.new(*HOST_AND_PORT)
|
67
|
+
response = client.call(name, version, params)
|
68
|
+
if show_result
|
69
|
+
output "Got a response:"
|
70
|
+
output " #{response.status}"
|
71
|
+
output " #{response.data.inspect}"
|
72
|
+
end
|
73
|
+
rescue Exception => exception
|
74
|
+
puts "FAILED -> #{exception.class}: #{exception.message}"
|
75
|
+
puts exception.backtrace.join("\n")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def output(message, puts = true)
|
81
|
+
method = puts ? :puts : :print
|
82
|
+
self.send(method, message)
|
83
|
+
@file.send(method, message)
|
84
|
+
STDOUT.flush if method == :print
|
85
|
+
end
|
86
|
+
|
87
|
+
def round_and_display(time_in_ms)
|
88
|
+
self.display_time(self.round_time(time_in_ms))
|
89
|
+
end
|
90
|
+
|
91
|
+
def round_time(time_in_ms)
|
92
|
+
(time_in_ms * TIME_MODIFIER).to_i / TIME_MODIFIER.to_f
|
93
|
+
end
|
94
|
+
|
95
|
+
def display_time(time)
|
96
|
+
integer, fractional = time.to_s.split('.')
|
97
|
+
[ integer, fractional.ljust(4, '0') ].join('.')
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
data/bench/services.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
class BenchHost
|
2
|
+
include Sanford::Host
|
3
|
+
|
4
|
+
self.port = 12000
|
5
|
+
self.pid_dir = File.expand_path("../../tmp", __FILE__)
|
6
|
+
|
7
|
+
version 'v1' do
|
8
|
+
service 'simple', 'BenchHost::Simple'
|
9
|
+
end
|
10
|
+
|
11
|
+
class Simple
|
12
|
+
include Sanford::ServiceHandler
|
13
|
+
|
14
|
+
def run!
|
15
|
+
{ :string => 'test', :int => 1, :float => 2.1, :boolean => true,
|
16
|
+
:hash => { :something => 'else' }, :array => [ 1, 2, 3 ],
|
17
|
+
:request_number => self.request.params['request_number']
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/bench/tasks.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
namespace :bench do
|
2
|
+
|
3
|
+
task :load do
|
4
|
+
require 'bench/runner'
|
5
|
+
end
|
6
|
+
|
7
|
+
desc "Run a Benchmark report against the Benchmark server"
|
8
|
+
task :report => :load do
|
9
|
+
Bench::Runner.new.build_report
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Run Benchmark requests against the 'simple' service"
|
13
|
+
task :simple, [ :times ] => :load do |t, args|
|
14
|
+
runner = Bench::Runner.new(:output => '/dev/null')
|
15
|
+
runner.benchmark_service('v1', 'simple', {}, args[:times] || 1, true)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'ns-options'
|
2
|
+
require 'pathname'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
ENV['SANFORD_SERVICES_CONFIG'] ||= 'config/services'
|
6
|
+
|
7
|
+
module Sanford
|
8
|
+
|
9
|
+
module Config
|
10
|
+
include NsOptions::Proxy
|
11
|
+
|
12
|
+
option :hosts, Set, :default => []
|
13
|
+
option :services_config, Pathname, :default => ENV['SANFORD_SERVICES_CONFIG']
|
14
|
+
|
15
|
+
# We want class names to take precedence over a configured name, so that if
|
16
|
+
# a user specifies a specific class, they always get it
|
17
|
+
def self.find_host(name)
|
18
|
+
self.find_host_by_class_name(name) || self.find_host_by_name(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def self.find_host_by_class_name(class_name)
|
24
|
+
self.hosts.detect{|host_class| host_class.to_s == class_name.to_s }
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.find_host_by_name(name)
|
28
|
+
self.hosts.detect{|host_class| host_class.name == name.to_s }
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Sanford's connection class is an extesion of the connection class provided by
|
2
|
+
# Sanford-Protocol. It provides the main process of reading a request, routing
|
3
|
+
# it and writing a response. All requests are benchmarked and logged. The
|
4
|
+
# connection's `process` method should always try to return a response, so that
|
5
|
+
# clients do not have to timeout.
|
6
|
+
#
|
7
|
+
# Notes:
|
8
|
+
# * This class is separated from `Sanford::Server` to help with thread safety.
|
9
|
+
# The server creates a new instance of this class per connection, which means
|
10
|
+
# there is a separate connection per thread.
|
11
|
+
#
|
12
|
+
require 'benchmark'
|
13
|
+
require 'sanford-protocol'
|
14
|
+
|
15
|
+
require 'sanford/exceptions'
|
16
|
+
|
17
|
+
module Sanford
|
18
|
+
|
19
|
+
class Connection < Sanford::Protocol::Connection
|
20
|
+
|
21
|
+
DEFAULT_TIMEOUT = 1
|
22
|
+
|
23
|
+
attr_reader :service_host, :logger, :exception_handler, :timeout
|
24
|
+
|
25
|
+
def initialize(service_host, client_socket)
|
26
|
+
@service_host = service_host
|
27
|
+
@exception_handler = self.service_host.exception_handler
|
28
|
+
@logger = self.service_host.logger
|
29
|
+
@timeout = (ENV['SANFORD_TIMEOUT'] || DEFAULT_TIMEOUT).to_f
|
30
|
+
super(client_socket)
|
31
|
+
end
|
32
|
+
|
33
|
+
def process
|
34
|
+
response = nil
|
35
|
+
self.logger.info("Received request")
|
36
|
+
benchmark = Benchmark.measure do
|
37
|
+
begin
|
38
|
+
request = Sanford::Protocol::Request.parse(self.read(self.timeout))
|
39
|
+
self.log_request(request)
|
40
|
+
response = Sanford::Protocol::Response.new(*self.run(request))
|
41
|
+
rescue Exception => exception
|
42
|
+
handler = self.exception_handler.new(exception, self.logger)
|
43
|
+
response = handler.response
|
44
|
+
ensure
|
45
|
+
self.write(response.to_hash)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
time_taken = self.round_time(benchmark.real)
|
49
|
+
self.logger.info("Completed in #{time_taken}ms #{response.status}\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def run(request)
|
55
|
+
self.service_host.run(request)
|
56
|
+
end
|
57
|
+
|
58
|
+
def log_request(request)
|
59
|
+
self.logger.info(" Version: #{request.version.inspect}")
|
60
|
+
self.logger.info(" Service: #{request.name.inspect}")
|
61
|
+
self.logger.info(" Parameters: #{request.params.inspect}")
|
62
|
+
end
|
63
|
+
|
64
|
+
def round_time(time_in_seconds)
|
65
|
+
((time_in_seconds * 1000.to_f) + 0.5).to_i
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Sanford's exception handler class takes an exception and builds a valid
|
2
|
+
# response. For certain exceptions, Sanford will use special response codes and
|
3
|
+
# for all others it will classify them as generic error requests.
|
4
|
+
#
|
5
|
+
require 'sanford-protocol'
|
6
|
+
|
7
|
+
require 'sanford/exceptions'
|
8
|
+
|
9
|
+
module Sanford
|
10
|
+
|
11
|
+
class ExceptionHandler
|
12
|
+
attr_reader :exception, :logger
|
13
|
+
|
14
|
+
def initialize(exception, logger)
|
15
|
+
@exception = exception
|
16
|
+
@logger = logger
|
17
|
+
end
|
18
|
+
|
19
|
+
def response
|
20
|
+
self.logger.error("#{exception.class}: #{exception.message}")
|
21
|
+
self.logger.error(exception.backtrace.join("\n"))
|
22
|
+
status = Sanford::Protocol::ResponseStatus.new(*self.determine_code_and_message)
|
23
|
+
Sanford::Protocol::Response.new(status)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def determine_code_and_message
|
29
|
+
case(self.exception)
|
30
|
+
when Sanford::Protocol::BadMessageError, Sanford::Protocol::BadRequestError
|
31
|
+
[ :bad_request, self.exception.message ]
|
32
|
+
when Sanford::NotFoundError
|
33
|
+
[ :not_found ]
|
34
|
+
when Sanford::Protocol::TimeoutError
|
35
|
+
[ :timeout ]
|
36
|
+
when Exception
|
37
|
+
[ :error, "An unexpected error occurred." ]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Sanford
|
2
|
+
|
3
|
+
class BaseError < RuntimeError; end
|
4
|
+
|
5
|
+
class NotFoundError < BaseError; end
|
6
|
+
|
7
|
+
class NoHostError < BaseError
|
8
|
+
attr_reader :message
|
9
|
+
|
10
|
+
def initialize(host_name)
|
11
|
+
@message = if Sanford.config.hosts.empty?
|
12
|
+
"No hosts have been defined. " \
|
13
|
+
"Please define a host before trying to run Sanford."
|
14
|
+
else
|
15
|
+
"A host couldn't be found with the name #{host_name.inspect}. "
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class InvalidHostError < BaseError
|
21
|
+
attr_reader :message
|
22
|
+
|
23
|
+
def initialize(host)
|
24
|
+
@message = "A port must be configured or provided to build an instance of '#{host}'"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class NoHandlerClassError < BaseError
|
29
|
+
attr_reader :message
|
30
|
+
|
31
|
+
def initialize(host, handler_class_name)
|
32
|
+
@message = "Sanford couldn't find the service handler '#{handler_class_name}'." \
|
33
|
+
"It doesn't exist or hasn't been required in yet."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|