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