serf 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +10 -4
- data/Gemfile.lock +27 -17
- data/README.md +317 -120
- data/docs/thread_pools.txt +16 -0
- data/lib/serf/builder.rb +126 -162
- data/lib/serf/command.rb +113 -0
- data/lib/serf/message.rb +16 -2
- data/lib/serf/messages/caught_exception_event.rb +10 -7
- data/lib/serf/routing/endpoint.rb +49 -0
- data/lib/serf/routing/registry.rb +66 -0
- data/lib/serf/runners/direct.rb +52 -0
- data/lib/serf/runners/event_machine.rb +71 -0
- data/lib/serf/runners/girl_friday.rb +73 -0
- data/lib/serf/runners/helper.rb +23 -0
- data/lib/serf/serfer.rb +112 -25
- data/lib/serf/util/{with_error_handling.rb → error_handling.rb} +23 -10
- data/lib/serf/util/options_extraction.rb +106 -0
- data/lib/serf/version.rb +2 -2
- data/serf.gemspec +31 -20
- metadata +60 -33
- data/lib/serf/runners/direct_runner.rb +0 -60
- data/lib/serf/runners/em_runner.rb +0 -62
- data/lib/serf/util/route_endpoint.rb +0 -37
- data/lib/serf/util/route_set.rb +0 -82
@@ -0,0 +1,16 @@
|
|
1
|
+
Serf drives the incoming requests using runners. Each runner
|
2
|
+
is implemented to either perform tasks synchronously or asynchronously.
|
3
|
+
For asynchronous background processing, we rely on third party
|
4
|
+
libraries. The following is a list of current and (hopefully) future
|
5
|
+
libraries we hope to support.
|
6
|
+
|
7
|
+
Implemented Runners:
|
8
|
+
* EventMachine http://rubyeventmachine.com/
|
9
|
+
* GirlFriday https://github.com/mperham/girl_friday
|
10
|
+
|
11
|
+
TBD ThreadPool Libraries:
|
12
|
+
* ThreadPool https://github.com/danielbush/ThreadPool
|
13
|
+
* Threadz https://github.com/nanodeath/threadz
|
14
|
+
* Celluloid https://github.com/tarcieri/celluloid
|
15
|
+
* Resque https://github.com/defunkt/resque
|
16
|
+
* DelayedJob https://github.com/tobi/delayed_job
|
data/lib/serf/builder.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
+
require 'serf/routing/endpoint'
|
2
|
+
require 'serf/routing/registry'
|
3
|
+
require 'serf/runners/direct'
|
1
4
|
require 'serf/serfer'
|
2
|
-
require 'serf/runners/direct_runner'
|
3
|
-
require 'serf/runners/em_runner'
|
4
5
|
require 'serf/util/null_object'
|
5
|
-
require 'serf/util/
|
6
|
+
require 'serf/util/options_extraction'
|
6
7
|
|
7
8
|
module Serf
|
8
9
|
|
9
10
|
##
|
10
11
|
# A Serf Builder that processes the SerfUp DSL to build a rack-like
|
11
|
-
# app to
|
12
|
+
# app to endpoint and process received messages. This builder is
|
12
13
|
# implemented based on code from Rack::Builder.
|
13
14
|
#
|
14
15
|
# builder = Serf::Builder.parse_file 'examples/config.su'
|
@@ -22,6 +23,8 @@ module Serf
|
|
22
23
|
# builder.to_app
|
23
24
|
#
|
24
25
|
class Builder
|
26
|
+
include Serf::Util::OptionsExtraction
|
27
|
+
|
25
28
|
def self.parse_file(config)
|
26
29
|
cfgfile = ::File.read(config)
|
27
30
|
builder = eval "::Serf::Builder.new {\n" + cfgfile + "\n}",
|
@@ -29,33 +32,27 @@ module Serf
|
|
29
32
|
return builder
|
30
33
|
end
|
31
34
|
|
32
|
-
def initialize(
|
33
|
-
|
34
|
-
@use = []
|
35
|
-
@route_maps = []
|
36
|
-
@handlers = {}
|
37
|
-
@message_parsers = {}
|
38
|
-
@not_found = app || proc do
|
39
|
-
raise ArgumentError, 'Handler Not Found'
|
40
|
-
end
|
41
|
-
|
42
|
-
# Default option in route_configs for background is 'false'
|
43
|
-
@background = false
|
35
|
+
def initialize(*args, &block)
|
36
|
+
extract_options! args
|
44
37
|
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
@background_runner_factory = ::Serf::Runners::EmRunner
|
51
|
-
@route_set_factory = ::Serf::Util::RouteSet
|
38
|
+
# Configuration about the endpoints and apps to run.
|
39
|
+
@use = []
|
40
|
+
@not_found = opts :not_found, lambda { |env|
|
41
|
+
raise ArgumentError, 'Endpoints Not Found'
|
42
|
+
}
|
52
43
|
|
53
|
-
# Utility and messaging channels
|
54
|
-
#
|
55
|
-
@
|
44
|
+
# Utility and messaging channels that get passed as options
|
45
|
+
# when building our Runners and Handlers.
|
46
|
+
@response_channel = ::Serf::Util::NullObject.new
|
56
47
|
@error_channel = ::Serf::Util::NullObject.new
|
57
48
|
@logger = ::Serf::Util::NullObject.new
|
58
49
|
|
50
|
+
# Set up the starting state for our DSL calls.
|
51
|
+
@runner_matcher_endpoint_map = {}
|
52
|
+
@runner_params = {}
|
53
|
+
runner :direct
|
54
|
+
@matcher = nil
|
55
|
+
|
59
56
|
# configure based on a given block.
|
60
57
|
instance_eval(&block) if block_given?
|
61
58
|
end
|
@@ -68,126 +65,130 @@ module Serf
|
|
68
65
|
@use << proc { |app| middleware.new(app, *args, &block) }
|
69
66
|
end
|
70
67
|
|
71
|
-
def
|
72
|
-
@route_maps << route_map
|
73
|
-
end
|
68
|
+
def not_found(app); @not_found = app; end
|
74
69
|
|
75
|
-
def
|
76
|
-
|
77
|
-
end
|
70
|
+
def response_channel(channel); @response_channel = channel; end
|
71
|
+
def error_channel(channel); @error_channel = channel; end
|
72
|
+
def logger(logger); @logger = logger; end
|
78
73
|
|
79
|
-
|
80
|
-
|
81
|
-
|
74
|
+
##
|
75
|
+
# DSL Method to change our current context to use the given matcher.
|
76
|
+
#
|
77
|
+
def match(matcher); @matcher = matcher; end
|
82
78
|
|
83
|
-
|
84
|
-
|
85
|
-
|
79
|
+
##
|
80
|
+
# Mount and endpoint to the current context's Runner and Matcher.
|
81
|
+
# Connected so the endpoint will pass serf_options to the handler's build.
|
82
|
+
def run(*args, &block); mount true, *args, █ end
|
86
83
|
|
87
|
-
|
88
|
-
|
89
|
-
|
84
|
+
##
|
85
|
+
# Mount and endpoint to the current context's Runner and Matcher.
|
86
|
+
# Unconnected so the endpoint will omit serf_options to the handler's build.
|
87
|
+
def run_unconnected(*args, &block); mount false, *args, █ end
|
90
88
|
|
91
|
-
|
92
|
-
|
89
|
+
##
|
90
|
+
# The generic mount method used by run & run_unconnected to create an
|
91
|
+
# endpoint to be associated with the current context's Runner and Matcher.
|
92
|
+
def mount(connected, handler_factory, *args, &block)
|
93
|
+
raise 'No matcher defined yet' unless @matcher
|
94
|
+
@runner_matcher_endpoint_map[@runner_factory] ||= {}
|
95
|
+
@runner_matcher_endpoint_map[@runner_factory][@matcher] ||= []
|
96
|
+
@runner_matcher_endpoint_map[@runner_factory][@matcher] <<
|
97
|
+
Serf::Routing::Endpoint.new(
|
98
|
+
connected,
|
99
|
+
handler_factory,
|
100
|
+
*args,
|
101
|
+
&block)
|
93
102
|
end
|
94
103
|
|
95
|
-
|
96
|
-
|
104
|
+
##
|
105
|
+
# DSL Method to change our current context to use the given runner.
|
106
|
+
#
|
107
|
+
def runner(type)
|
108
|
+
@runner_factory = case type
|
109
|
+
when :direct
|
110
|
+
::Serf::Runners::Direct
|
111
|
+
when :event_machine
|
112
|
+
begin
|
113
|
+
require 'serf/runners/event_machine'
|
114
|
+
Serf::Runners::EventMachine
|
115
|
+
rescue NameError => e
|
116
|
+
e.extend Serf::Error
|
117
|
+
raise e
|
118
|
+
end
|
119
|
+
when :girl_friday
|
120
|
+
begin
|
121
|
+
require 'serf/runners/girl_friday'
|
122
|
+
Serf::Runners::GirlFriday
|
123
|
+
rescue NameError => e
|
124
|
+
e.extend Serf::Error
|
125
|
+
raise e
|
126
|
+
end
|
127
|
+
else
|
128
|
+
raise 'No callable runner' unless type.respond_to? :build
|
129
|
+
type
|
130
|
+
end
|
97
131
|
end
|
98
132
|
|
99
|
-
def
|
100
|
-
@
|
133
|
+
def params(*args)
|
134
|
+
@runner_params[@runner_factory] = args
|
101
135
|
end
|
102
136
|
|
103
|
-
|
104
|
-
|
137
|
+
##
|
138
|
+
# Returns a hash of our current serf infrastructure options
|
139
|
+
# to be passed to Endpoint#build methods.
|
140
|
+
def serf_options
|
141
|
+
{
|
142
|
+
response_channel: @error_channel,
|
143
|
+
error_channel: @error_channel,
|
144
|
+
logger: @logger
|
145
|
+
}
|
105
146
|
end
|
106
147
|
|
107
|
-
|
108
|
-
|
109
|
-
|
148
|
+
##
|
149
|
+
# Create our app.
|
150
|
+
#
|
151
|
+
def to_app
|
152
|
+
# By default, we go to the not_found app.
|
153
|
+
app = @not_found
|
110
154
|
|
111
|
-
|
112
|
-
@error_channel = error_channel
|
113
|
-
end
|
155
|
+
registries = {}
|
114
156
|
|
115
|
-
|
116
|
-
@
|
117
|
-
end
|
157
|
+
# Set additional options for all the mounts
|
158
|
+
@runner_matcher_endpoint_map.each do |runner_factory, matcher_endpoints|
|
118
159
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
route_configs_iterator(route_configs).each do |route_config|
|
126
|
-
# If the passed in route_config was a String, then we place
|
127
|
-
# it in an route config as the 'target' field and leave all
|
128
|
-
# other options as default.
|
129
|
-
config = (route_config.is_a?(String) ?
|
130
|
-
{ target: route_config } :
|
131
|
-
route_config)
|
132
|
-
|
133
|
-
# Get the required handler.
|
134
|
-
# Raises error if handler wasn't declared in config.
|
135
|
-
target = config.fetch :target
|
136
|
-
handler_name, action = handler_and_action target
|
137
|
-
|
138
|
-
# Raises error if handler wasn't registered with builder.
|
139
|
-
handler = @handlers.fetch handler_name
|
140
|
-
|
141
|
-
# Lookup the parser if it was defined.
|
142
|
-
# The Parser MAY be either an object or string.
|
143
|
-
# If String, then we're going to look up in parser map.
|
144
|
-
# Raises an error if a parser (string) was declared, but not
|
145
|
-
# registered with the builder.
|
146
|
-
parser = config[:message_parser]
|
147
|
-
parser = @message_parsers.fetch(parser) if parser.is_a?(String)
|
148
|
-
|
149
|
-
# We have the handler, action and parser.
|
150
|
-
# Now we're going to add that route to either the background
|
151
|
-
# or foreground route_set.
|
152
|
-
background = config.fetch(:background) { @background }
|
153
|
-
(background ? bg_route_set : fg_route_set).add_route(
|
154
|
-
matcher: matcher,
|
155
|
-
handler: handler,
|
156
|
-
action: action,
|
157
|
-
message_parser: parser)
|
158
|
-
end
|
160
|
+
# 1. Create a registry for our given hash of matchers to endpoints.
|
161
|
+
# 2. Convert the hash of matcher to endpoints into a useable registry
|
162
|
+
# for lookups.
|
163
|
+
registry = ::Serf::Routing::Registry.new
|
164
|
+
matcher_endpoints.each do |matcher, endpoints|
|
165
|
+
registry.add matcher, endpoints
|
159
166
|
end
|
160
|
-
end
|
161
|
-
|
162
|
-
# Get or make our foreground runner
|
163
|
-
fg_runner = @foreground_runner_factory.build(
|
164
|
-
results_channel: @results_channel,
|
165
|
-
error_channel: @error_channel,
|
166
|
-
logger: @logger)
|
167
|
-
|
168
|
-
# Get or make our background runner
|
169
|
-
bg_runner = @background_runner_factory.build(
|
170
|
-
results_channel: @results_channel,
|
171
|
-
error_channel: @error_channel,
|
172
|
-
logger: @logger)
|
173
167
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
168
|
+
# Ok, we'll create the runner and add it to our registries hash
|
169
|
+
# if we actually have endpoints here.
|
170
|
+
if registry.size > 0
|
171
|
+
runner_params = @runner_params[runner_factory] ?
|
172
|
+
@runner_params[runner_factory] :
|
173
|
+
[]
|
174
|
+
runner_params << (runner_params.last.is_a?(Hash) ?
|
175
|
+
runner_params.pop.merge(serf_options) :
|
176
|
+
serf_options)
|
177
|
+
runner = runner_factory.build *runner_params
|
178
|
+
registries[runner] = registry
|
179
|
+
end
|
181
180
|
end
|
182
181
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
app = @serfer_factory.build(
|
189
|
-
route_sets: route_sets,
|
182
|
+
if registries.size > 0
|
183
|
+
app = Serf::Serfer.build(
|
184
|
+
# The registries to match, and their runners to execute.
|
185
|
+
registries: registries,
|
186
|
+
# App if no endpoints were found.
|
190
187
|
not_found: app,
|
188
|
+
# Serf infrastructure options to pass to 'connected' Endpoints
|
189
|
+
# to build a handler instance for each env hash received.
|
190
|
+
serf_options: serf_options,
|
191
|
+
# Options to use by serfer because it includes ErrorHandling.
|
191
192
|
error_channel: @error_channel,
|
192
193
|
logger: @logger)
|
193
194
|
end
|
@@ -197,42 +198,5 @@ module Serf
|
|
197
198
|
|
198
199
|
return app
|
199
200
|
end
|
200
|
-
|
201
|
-
private
|
202
|
-
|
203
|
-
##
|
204
|
-
# This handles route_configs that are Array, Hash or String.
|
205
|
-
# We want to create a proper iterator to run over the route_configs.
|
206
|
-
def route_configs_iterator(route_configs)
|
207
|
-
case route_configs
|
208
|
-
when String
|
209
|
-
return Array(route_configs)
|
210
|
-
when Hash
|
211
|
-
return [route_configs]
|
212
|
-
else
|
213
|
-
return route_configs
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
##
|
218
|
-
# Extracts the handler_name and action from the 'target' using
|
219
|
-
# the shortcut convention similar to Rails routing.
|
220
|
-
#
|
221
|
-
# 'my_handler#my_method' => # my_method action.
|
222
|
-
# 'my_handler#' => # action defaults to 'call' method.
|
223
|
-
# 'my_handler' => # action defaults to 'call' method.
|
224
|
-
# '#my_method' => # some registered handler name with empty string.
|
225
|
-
#
|
226
|
-
# @param [String] target the handler and action description.
|
227
|
-
# @return the splat handler and action.
|
228
|
-
#
|
229
|
-
def handler_and_action(target)
|
230
|
-
handler, action = target.split '#', 2
|
231
|
-
handler = handler.to_s.strip
|
232
|
-
action = action.to_s.strip
|
233
|
-
action = :call if action.size == 0
|
234
|
-
return handler, action
|
235
|
-
end
|
236
201
|
end
|
237
|
-
|
238
202
|
end
|
data/lib/serf/command.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/class/attribute'
|
3
|
+
|
4
|
+
require 'serf/util/options_extraction'
|
5
|
+
|
6
|
+
module Serf
|
7
|
+
|
8
|
+
##
|
9
|
+
# A base class for Serf users to implement a Command pattern.
|
10
|
+
#
|
11
|
+
# class MyCommand
|
12
|
+
# include Serf::Command
|
13
|
+
#
|
14
|
+
# # Set a default Message Parser for the class.
|
15
|
+
# self.request_factory = MySerfRequestMessage
|
16
|
+
#
|
17
|
+
# def initialize(*args, &block)
|
18
|
+
# # Do some validation here, or extra parameter setting with the args
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# def call
|
22
|
+
# # Do something w/ @request and @opts
|
23
|
+
# return nil # e.g. MySerfMessage
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# MyCommand.call(REQUEST_ENV, some, extra, params, options_hash, &block)
|
28
|
+
#
|
29
|
+
# # Built in lambda wrapping to use the MyCommand with GirlFriday.
|
30
|
+
# worker = MyCommand.worker some, extra, params, options_hash, &block
|
31
|
+
# work_queue = GirlFriday::WorkQueue.new &worker
|
32
|
+
# work_queue.push REQUEST_ENV
|
33
|
+
#
|
34
|
+
module Command
|
35
|
+
extend ActiveSupport::Concern
|
36
|
+
include Serf::Util::OptionsExtraction
|
37
|
+
|
38
|
+
included do
|
39
|
+
class_attribute :request_factory
|
40
|
+
attr_reader :request
|
41
|
+
end
|
42
|
+
|
43
|
+
def call
|
44
|
+
raise NotImplementedError
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate_request!
|
48
|
+
# We must verify that the request is valid, but only if the
|
49
|
+
# request object isn't a hash.
|
50
|
+
unless request.is_a?(Hash) || request.valid?
|
51
|
+
raise ArgumentError, request.full_error_messages
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module ClassMethods
|
56
|
+
|
57
|
+
##
|
58
|
+
# Class method that both builds then executes the unit of work.
|
59
|
+
#
|
60
|
+
def call(*args, &block)
|
61
|
+
self.build(*args, &block).call
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Factory build method that creates an object of the implementing
|
66
|
+
# class' unit of work with the given parameters.
|
67
|
+
#
|
68
|
+
def build(*args, &block)
|
69
|
+
# The very first argument is the Request, we shift it off the args var.
|
70
|
+
req = args.shift
|
71
|
+
req = {} if req.nil?
|
72
|
+
|
73
|
+
# Now we allocate the object, and do some options extraction that may
|
74
|
+
# modify the args array by popping off the last element if it is a hash.
|
75
|
+
obj = allocate
|
76
|
+
obj.send :__send__, :extract_options!, args
|
77
|
+
|
78
|
+
# If the request was a hash, we MAY be able to convert it into a
|
79
|
+
# request object. We only do this if a request_factory was set either
|
80
|
+
# in the options, or if the request_factory class attribute is set.
|
81
|
+
# Otherwise, just give the command the hash, and it is up to them
|
82
|
+
# to understand what was given to it.
|
83
|
+
factory = obj.opts :request_factory, self.request_factory
|
84
|
+
request = (req.is_a?(Hash) && factory ? factory.build(req) : req)
|
85
|
+
|
86
|
+
# Set the request instance variable to whatever type of request we got.
|
87
|
+
obj.instance_variable_set :@request, request
|
88
|
+
|
89
|
+
# Now validate that the request is ok.
|
90
|
+
# Implementing classes MAY override this method to do different
|
91
|
+
# kind of request validation.
|
92
|
+
obj.validate_request!
|
93
|
+
|
94
|
+
# Finalize the object's construction with the rest of the args & block.
|
95
|
+
obj.send :__send__, :initialize, *args, &block
|
96
|
+
|
97
|
+
return obj
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Generates a curried function to execute a Command's call class method.
|
102
|
+
#
|
103
|
+
# @returns lambda block to execute a call.
|
104
|
+
#
|
105
|
+
def worker(*args, &block)
|
106
|
+
lambda { |message|
|
107
|
+
self.call message, *args, &block
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/serf/message.rb
CHANGED
@@ -15,6 +15,8 @@ module Serf
|
|
15
15
|
included do
|
16
16
|
class_attribute :kind
|
17
17
|
send 'kind=', self.to_s.tableize.singularize
|
18
|
+
class_attribute :model_name
|
19
|
+
send 'model_name=', self.to_s
|
18
20
|
end
|
19
21
|
|
20
22
|
def kind
|
@@ -33,10 +35,22 @@ module Serf
|
|
33
35
|
to_hash.to_json *args
|
34
36
|
end
|
35
37
|
|
38
|
+
def model
|
39
|
+
self.class
|
40
|
+
end
|
41
|
+
|
42
|
+
def full_error_messages
|
43
|
+
errors.full_messages.join '. '
|
44
|
+
end
|
45
|
+
|
36
46
|
module ClassMethods
|
37
47
|
|
38
|
-
def parse(*args)
|
39
|
-
self.new *args
|
48
|
+
def parse(*args, &block)
|
49
|
+
self.new *args, &block
|
50
|
+
end
|
51
|
+
|
52
|
+
def build(*args, &block)
|
53
|
+
self.new *args, &block
|
40
54
|
end
|
41
55
|
|
42
56
|
end
|
@@ -9,7 +9,7 @@ module Messages
|
|
9
9
|
# exception during the processing of some message, which is
|
10
10
|
# represented by the context field.
|
11
11
|
#
|
12
|
-
# Instances of this class are norminally
|
12
|
+
# Instances of this class are norminally pushed to an
|
13
13
|
# error channel for out of band processing/notification.
|
14
14
|
#
|
15
15
|
class CaughtExceptionEvent
|
@@ -17,21 +17,24 @@ module Messages
|
|
17
17
|
include ::Serf::Util::Uuidable
|
18
18
|
|
19
19
|
attr_accessor :context
|
20
|
-
attr_accessor :
|
21
|
-
attr_accessor :
|
20
|
+
attr_accessor :error
|
21
|
+
attr_accessor :message
|
22
|
+
attr_accessor :backtrace
|
22
23
|
|
23
24
|
def initialize(options={})
|
24
25
|
@context = options[:context]
|
25
|
-
@
|
26
|
-
@
|
26
|
+
@error = options[:error]
|
27
|
+
@message = options[:message]
|
28
|
+
@backtrace = options[:backtrace]
|
27
29
|
@uuid = options[:uuid]
|
28
30
|
end
|
29
31
|
|
30
32
|
def attributes
|
31
33
|
{
|
32
34
|
'context' => @context,
|
33
|
-
'
|
34
|
-
'
|
35
|
+
'error' => @error,
|
36
|
+
'message' => @message,
|
37
|
+
'backtrace' => @backtrace,
|
35
38
|
'uuid' => uuid
|
36
39
|
}
|
37
40
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'serf/util/options_extraction'
|
2
|
+
|
3
|
+
module Serf
|
4
|
+
module Routing
|
5
|
+
|
6
|
+
##
|
7
|
+
# An endpoint is the description of how to build a Unit of Work
|
8
|
+
# for a given matched message. It builds an instance that
|
9
|
+
# responds to the `call` method that will actually execute the work.
|
10
|
+
# Units of work is built on every received message with the request,
|
11
|
+
# given arguments, options (merged with serf infrastructure options)
|
12
|
+
# and given block.
|
13
|
+
#
|
14
|
+
class Endpoint
|
15
|
+
include Serf::Util::OptionsExtraction
|
16
|
+
|
17
|
+
def initialize(connect, handler_factory, *args, &block)
|
18
|
+
# If we want to connect serf options, then we try to extract
|
19
|
+
# any possible options from the args list. If a hash exists at the
|
20
|
+
# end of the args list, then we'll merge into it. Otherwise a new hash
|
21
|
+
# will be added on.
|
22
|
+
extract_options! args if @connect = connect
|
23
|
+
|
24
|
+
@handler_factory= handler_factory
|
25
|
+
@args = args
|
26
|
+
@block = block
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Builds a Unit of Work object.
|
31
|
+
#
|
32
|
+
def build(env, serf_options={})
|
33
|
+
# If we are connecting serf options, then we need to pass these
|
34
|
+
# options on to the builder.
|
35
|
+
if @connect
|
36
|
+
@handler_factory.build(
|
37
|
+
env.dup,
|
38
|
+
*@args,
|
39
|
+
options.merge(serf_options),
|
40
|
+
&@block)
|
41
|
+
else
|
42
|
+
@handler_factory.build env.dup, *@args, &@block
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'serf/util/regexp_matcher'
|
2
|
+
|
3
|
+
module Serf
|
4
|
+
module Routing
|
5
|
+
|
6
|
+
##
|
7
|
+
# EndpointRegistry returns list of Endpoints to execute that match
|
8
|
+
# criteria based on the Endpoints' associated 'matcher' object
|
9
|
+
# with the input of the ENV Hash (passed to match).
|
10
|
+
#
|
11
|
+
class Registry
|
12
|
+
|
13
|
+
def initialize(options={})
|
14
|
+
@endpoints = {}
|
15
|
+
@matchers = []
|
16
|
+
@regexp_matcher_factory = options.fetch(:regexp_matcher_factory) {
|
17
|
+
::Serf::Util::RegexpMatcher
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Connects a matcher (String or an Object implementing ===) to endpoints.
|
23
|
+
#
|
24
|
+
def add(matcher, endpoints)
|
25
|
+
# Maybe we have an non-String matcher. Handle the Regexp case.
|
26
|
+
# We only keep track of matchers if it isn't a string because
|
27
|
+
# string matchers are just pulled out of endpoints by key lookup.
|
28
|
+
matcher = @regexp_matcher_factory.build matcher if matcher.kind_of? Regexp
|
29
|
+
@matchers << matcher unless matcher.is_a? String
|
30
|
+
|
31
|
+
# We add the (matcher+endpoint) into our endpoints
|
32
|
+
@endpoints[matcher] ||= []
|
33
|
+
@endpoints[matcher].concat endpoints
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# @param [Hash] env The input message environment to match for endpoints.
|
38
|
+
# @return [Array] List of endpoints that matched.
|
39
|
+
#
|
40
|
+
def match(env={})
|
41
|
+
kind = env[:kind]
|
42
|
+
endpoints = []
|
43
|
+
endpoints.concat @endpoints.fetch(kind) { [] }
|
44
|
+
@matchers.each do |matcher|
|
45
|
+
endpoints.concat @endpoints[matcher] if matcher === env
|
46
|
+
end
|
47
|
+
return endpoints
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# @return [Integer] Number of matchers this EndpointsMap tracks.
|
52
|
+
#
|
53
|
+
def size
|
54
|
+
return @endpoints.size
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Default factory method.
|
59
|
+
#
|
60
|
+
def self.build(options={})
|
61
|
+
self.new options
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|