serf 0.6.1 → 0.7.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 +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
|