serf 0.9.0 → 0.10.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/README.md +106 -305
- data/lib/serf/builder.rb +92 -128
- data/lib/serf/command.rb +36 -73
- data/lib/serf/errors/not_found.rb +8 -0
- data/lib/serf/middleware/girl_friday_async.rb +39 -0
- data/lib/serf/middleware/masherize.rb +25 -0
- data/lib/serf/middleware/uuid_tagger.rb +14 -8
- data/lib/serf/{util → routing}/regexp_matcher.rb +12 -6
- data/lib/serf/routing/route.rb +35 -0
- data/lib/serf/routing/route_set.rb +64 -0
- data/lib/serf/serfer.rb +56 -114
- data/lib/serf/util/error_handling.rb +5 -9
- data/lib/serf/util/options_extraction.rb +16 -5
- data/lib/serf/util/protected_call.rb +3 -2
- data/lib/serf/util/uuidable.rb +28 -4
- data/lib/serf/version.rb +1 -1
- data/serf.gemspec +8 -11
- metadata +35 -38
- data/lib/serf/more/command_worker.rb +0 -45
- data/lib/serf/routing/endpoint.rb +0 -49
- data/lib/serf/routing/registry.rb +0 -66
- data/lib/serf/runners/direct.rb +0 -52
- data/lib/serf/runners/event_machine.rb +0 -69
- data/lib/serf/runners/girl_friday.rb +0 -69
- data/lib/serf/runners/helper.rb +0 -23
- data/lib/serf/util/mash_factory.rb +0 -18
data/lib/serf/builder.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
require 'serf/routing/
|
2
|
-
require 'serf/routing/
|
3
|
-
require 'serf/runners/direct'
|
1
|
+
require 'serf/routing/route'
|
2
|
+
require 'serf/routing/route_set'
|
4
3
|
require 'serf/serfer'
|
5
4
|
require 'serf/util/null_object'
|
6
5
|
require 'serf/util/options_extraction'
|
@@ -9,7 +8,7 @@ module Serf
|
|
9
8
|
|
10
9
|
##
|
11
10
|
# A Serf Builder that processes the SerfUp DSL to build a rack-like
|
12
|
-
# app to
|
11
|
+
# app to handlers that process received messages. This builder is
|
13
12
|
# implemented based on code from Rack::Builder.
|
14
13
|
#
|
15
14
|
# builder = Serf::Builder.parse_file 'examples/config.su'
|
@@ -25,47 +24,80 @@ module Serf
|
|
25
24
|
class Builder
|
26
25
|
include Serf::Util::OptionsExtraction
|
27
26
|
|
27
|
+
attr_reader :serfer_factory
|
28
|
+
attr_reader :route_set_factory
|
29
|
+
attr_reader :route_factory
|
30
|
+
|
28
31
|
def self.parse_file(config)
|
29
32
|
cfgfile = ::File.read(config)
|
30
|
-
builder = eval "
|
33
|
+
builder = eval "Serf::Builder.new {\n" + cfgfile + "\n}",
|
31
34
|
TOPLEVEL_BINDING, config
|
32
35
|
return builder
|
33
36
|
end
|
34
37
|
|
38
|
+
def self.app(*args, &block)
|
39
|
+
new(*args, &block).to_app
|
40
|
+
end
|
41
|
+
|
35
42
|
def initialize(*args, &block)
|
36
43
|
extract_options! args
|
37
44
|
|
38
|
-
#
|
45
|
+
# Our factories
|
46
|
+
@serfer_factory = opts :serfer_factory, Serf::Serfer
|
47
|
+
@route_set_factory = opts :route_set_factory, Serf::Routing::RouteSet
|
48
|
+
@route_factory = opts :route_factory, Serf::Routing::Route
|
49
|
+
|
50
|
+
# List of middleware to be executed (non-built form)
|
39
51
|
@use = []
|
40
|
-
@not_found = opts :not_found, lambda { |env|
|
41
|
-
raise ArgumentError, 'Endpoints Not Found'
|
42
|
-
}
|
43
52
|
|
44
|
-
#
|
45
|
-
#
|
46
|
-
@
|
47
|
-
|
48
|
-
|
53
|
+
# A list of "mounted", non-built, command handlers with their
|
54
|
+
# matcher and policies.
|
55
|
+
@runs = []
|
56
|
+
|
57
|
+
# List of default policies to be run (non-built form)
|
58
|
+
@default_policies = []
|
49
59
|
|
50
|
-
#
|
51
|
-
@runner_matcher_endpoint_map = {}
|
52
|
-
@runner_params = {}
|
53
|
-
runner :direct
|
60
|
+
# The current matcher
|
54
61
|
@matcher = nil
|
55
62
|
|
63
|
+
# Current policies to be run (PRE-built)
|
64
|
+
@policies = []
|
65
|
+
|
56
66
|
# configure based on a given block.
|
57
67
|
instance_eval(&block) if block_given?
|
58
68
|
end
|
59
69
|
|
60
|
-
|
61
|
-
|
70
|
+
##
|
71
|
+
# Append a policy to default policy chain. The default
|
72
|
+
# policy chain is used by any route that does not define
|
73
|
+
# at least one of its own policies.
|
74
|
+
#
|
75
|
+
# @param policy the policy factory to append
|
76
|
+
# @param *args the arguments to pass to the factory
|
77
|
+
# @param &block the block to pass to the factory
|
78
|
+
def default_policy(policy, *args, &block)
|
79
|
+
@default_policies << proc { policy.build(*args, &block) }
|
62
80
|
end
|
63
81
|
|
82
|
+
##
|
83
|
+
# Append a rack-like middleware
|
84
|
+
#
|
85
|
+
# @param the middleware class
|
86
|
+
# @param *args the arguments to pass to middleware.new
|
87
|
+
# @param &block the block to pass to middleware.new
|
64
88
|
def use(middleware, *args, &block)
|
65
89
|
@use << proc { |app| middleware.new(app, *args, &block) }
|
66
90
|
end
|
67
91
|
|
68
|
-
|
92
|
+
##
|
93
|
+
# Append a policy to the current match's policy chain.
|
94
|
+
#
|
95
|
+
# @param policy the policy factory to append
|
96
|
+
# @param *args the arguments to pass to the factory
|
97
|
+
# @param &block the block to pass to the factory
|
98
|
+
def policy(policy, *args, &block)
|
99
|
+
@policies << proc { policy.build(*args, &block) }
|
100
|
+
end
|
69
101
|
|
70
102
|
def response_channel(channel); @response_channel = channel; end
|
71
103
|
def error_channel(channel); @error_channel = channel; end
|
@@ -74,74 +106,34 @@ module Serf
|
|
74
106
|
##
|
75
107
|
# DSL Method to change our current context to use the given matcher.
|
76
108
|
#
|
77
|
-
def match(matcher)
|
78
|
-
|
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
|
83
|
-
|
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
|
88
|
-
|
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)
|
102
|
-
end
|
103
|
-
|
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
|
131
|
-
end
|
132
|
-
|
133
|
-
def params(*args)
|
134
|
-
@runner_params[@runner_factory] = args
|
109
|
+
def match(matcher)
|
110
|
+
@matcher = matcher
|
111
|
+
@policies = []
|
135
112
|
end
|
136
113
|
|
137
114
|
##
|
138
|
-
#
|
139
|
-
#
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
115
|
+
# @param command_factory the factory to invoke (in #to_app)
|
116
|
+
# @param *args the rest of the args to pass to command_factory#build method
|
117
|
+
# @param &block the block to pass to command_factory#build method
|
118
|
+
def run(command_factory, *args, &block)
|
119
|
+
raise 'No matcher defined yet' unless @matcher
|
120
|
+
# Create a local duplicate of the matcher and policies "snapshotted"
|
121
|
+
# at the time this method is called... so that snapshot is consistent
|
122
|
+
# for when the proc is called.
|
123
|
+
matcher = @matcher.dup
|
124
|
+
policies = @policies.dup
|
125
|
+
|
126
|
+
# This proc will be called in to_app when we actually go ahead and
|
127
|
+
# instantiate all the objects. By this point, route_set and
|
128
|
+
# default_policies passed to this proc will be ready, built.
|
129
|
+
@runs << proc { |route_set, default_policies|
|
130
|
+
route_set.add(
|
131
|
+
matcher,
|
132
|
+
route_factory.build(
|
133
|
+
command: command_factory.build(*args, &block),
|
134
|
+
policies: (policies.size > 0 ?
|
135
|
+
policies.map{ |p| p.call } :
|
136
|
+
default_policies)))
|
145
137
|
}
|
146
138
|
end
|
147
139
|
|
@@ -149,54 +141,26 @@ module Serf
|
|
149
141
|
# Create our app.
|
150
142
|
#
|
151
143
|
def to_app
|
152
|
-
#
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
166
|
-
end
|
167
|
-
|
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
|
180
|
-
end
|
181
|
-
|
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.
|
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.
|
192
|
-
error_channel: @error_channel,
|
193
|
-
logger: @logger)
|
144
|
+
# Create the route_set to resolve routes
|
145
|
+
route_set = route_set_factory.build
|
146
|
+
# Build the default policies to be used if routes did not specify any.
|
147
|
+
default_policies = @default_policies.map{ |p| p.call }
|
148
|
+
# Add each route to the route_set
|
149
|
+
for run in @runs
|
150
|
+
run.call route_set, default_policies
|
194
151
|
end
|
152
|
+
# Create our serfer class
|
153
|
+
app = serfer_factory.build(
|
154
|
+
route_set: route_set,
|
155
|
+
response_channel: (@response_channel || Serf::Util::NullObject.new),
|
156
|
+
error_channel: (@error_channel || Serf::Util::NullObject.new),
|
157
|
+
logger: (@logger || Serf::Util::NullObject.new))
|
195
158
|
|
196
159
|
# We're going to inject middleware here.
|
197
160
|
app = @use.reverse.inject(app) { |a,e| e[a] } if @use.size > 0
|
198
161
|
|
199
162
|
return app
|
200
163
|
end
|
164
|
+
|
201
165
|
end
|
202
166
|
end
|
data/lib/serf/command.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'active_support/concern'
|
2
2
|
require 'active_support/core_ext/class/attribute'
|
3
3
|
|
4
|
-
require 'serf/util/
|
4
|
+
require 'serf/util/error_handling'
|
5
5
|
require 'serf/util/options_extraction'
|
6
|
+
require 'serf/util/protected_call'
|
7
|
+
require 'serf/util/uuidable'
|
6
8
|
|
7
9
|
module Serf
|
8
10
|
|
@@ -12,103 +14,64 @@ module Serf
|
|
12
14
|
# class MyCommand
|
13
15
|
# include Serf::Command
|
14
16
|
#
|
15
|
-
#
|
16
|
-
# self.request_factory = MySerfRequestMessage
|
17
|
-
#
|
18
|
-
# # Validate the request, like using JSON-Schema for hash
|
19
|
-
# # (or Hashie's Mash) requests.
|
20
|
-
# self.request_validator = MySerfRequestValidator
|
21
|
-
#
|
22
|
-
# def initialize(*args, &block)
|
17
|
+
# def initialize(*contructor_params, &block)
|
23
18
|
# # Do some validation here, or extra parameter setting with the args
|
19
|
+
# @model = opts :model, MyModel
|
24
20
|
# end
|
25
21
|
#
|
26
|
-
# def call
|
27
|
-
# # Do something w/
|
28
|
-
#
|
22
|
+
# def call(request, context)
|
23
|
+
# # Do something w/ request, opts and context.
|
24
|
+
# item = @model.find request.model_id
|
25
|
+
# # create a new hashie of UUIDs, which we will use as the base
|
26
|
+
# # hash of our response
|
27
|
+
# response = create_uuids request
|
28
|
+
# response.kind = 'my_command/events/did_something'
|
29
|
+
# response.item = item
|
30
|
+
# return response
|
29
31
|
# end
|
30
32
|
# end
|
31
33
|
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# work_queue.push REQUEST_ENV
|
34
|
+
# constructor_params = [1, 2, 3, 4, etc]
|
35
|
+
# block = Proc.new {}
|
36
|
+
# request = ::Hashie::Mash.new
|
37
|
+
# context = ::Hashie::Mash.new user: current_user
|
38
|
+
# MyCommand.call(request, context, *contructor_params, &block)
|
38
39
|
#
|
39
40
|
module Command
|
40
41
|
extend ActiveSupport::Concern
|
41
|
-
include Serf::Util::OptionsExtraction
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
43
|
+
# Including Serf::Util::*... Order matters, kind of, here.
|
44
|
+
include Serf::Util::Uuidable
|
45
|
+
include Serf::Util::OptionsExtraction
|
46
|
+
include Serf::Util::ProtectedCall
|
47
|
+
include Serf::Util::ErrorHandling
|
49
48
|
|
50
|
-
def call
|
49
|
+
def call(request, context=nil *args, &block)
|
51
50
|
raise NotImplementedError
|
52
51
|
end
|
53
52
|
|
54
|
-
def validate_request!
|
55
|
-
request_validator.validate! request if request_validator
|
56
|
-
end
|
57
|
-
|
58
53
|
module ClassMethods
|
59
54
|
|
60
55
|
##
|
61
56
|
# Class method that both builds then executes the unit of work.
|
62
57
|
#
|
63
|
-
|
64
|
-
|
58
|
+
# @param request the request
|
59
|
+
# @param context the context about the request. Things like the
|
60
|
+
# requesting :user for ACL.
|
61
|
+
# @param *args remaining contructor arguments
|
62
|
+
# @param &block the block to pass to constructor
|
63
|
+
#
|
64
|
+
def call(request, context=::Hashie::Mash.new, *args, &block)
|
65
|
+
self.build(*args, &block).call(request, context)
|
65
66
|
end
|
66
67
|
|
67
68
|
##
|
68
69
|
# Factory build method that creates an object of the implementing
|
69
|
-
# class' unit of work with the given parameters.
|
70
|
+
# class' unit of work with the given parameters. By default,
|
71
|
+
# This just calls the class new method.
|
70
72
|
#
|
71
73
|
def build(*args, &block)
|
72
|
-
|
73
|
-
req = args.shift
|
74
|
-
req = {} if req.nil?
|
75
|
-
|
76
|
-
# Now we allocate the object, and do some options extraction that may
|
77
|
-
# modify the args array by popping off the last element if it is a hash.
|
78
|
-
obj = allocate
|
79
|
-
obj.__send__ :extract_options!, args
|
80
|
-
|
81
|
-
# If the request was a hash, we MAY be able to convert it into a
|
82
|
-
# request object. We only do this if a request_factory was set either
|
83
|
-
# in the options, or if the request_factory class attribute is set.
|
84
|
-
# Otherwise, just give the command the hash, and it is up to them
|
85
|
-
# to understand what was given to it.
|
86
|
-
factory = obj.opts :request_factory, self.request_factory
|
87
|
-
request = (req.is_a?(Hash) && factory ? factory.build(req) : req)
|
88
|
-
|
89
|
-
# Set the request instance variable to whatever type of request we got.
|
90
|
-
obj.instance_variable_set :@request, request
|
91
|
-
|
92
|
-
# Finalize the object's construction with the rest of the args & block.
|
93
|
-
obj.__send__ :initialize, *args, &block
|
94
|
-
|
95
|
-
# Now validate that the request is ok.
|
96
|
-
# Implementing classes MAY override this method to do different
|
97
|
-
# kind of request validation.
|
98
|
-
obj.validate_request!
|
99
|
-
|
100
|
-
return obj
|
101
|
-
end
|
102
|
-
|
103
|
-
##
|
104
|
-
# Generates a curried function to execute a Command's call class method.
|
105
|
-
#
|
106
|
-
# @returns lambda block to execute a call.
|
107
|
-
#
|
108
|
-
def worker(*args, &block)
|
109
|
-
lambda { |message|
|
110
|
-
self.call message, *args, &block
|
111
|
-
}
|
74
|
+
new *args, &block
|
112
75
|
end
|
113
76
|
|
114
77
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'girl_friday'
|
2
|
+
|
3
|
+
require 'serf/util/options_extraction'
|
4
|
+
require 'serf/util/uuidable'
|
5
|
+
|
6
|
+
module Serf
|
7
|
+
module Middleware
|
8
|
+
|
9
|
+
class GirlFridayAsync
|
10
|
+
include Serf::Util::OptionsExtraction
|
11
|
+
|
12
|
+
attr_reader :uuidable
|
13
|
+
attr_reader :queue
|
14
|
+
|
15
|
+
def initialize(app, *args)
|
16
|
+
extract_options! args
|
17
|
+
|
18
|
+
@uuidable = opts :uuidable, Serf::Util::Uuidable
|
19
|
+
|
20
|
+
@queue = ::GirlFriday::WorkQueue.new(
|
21
|
+
opts(:name, :serf_runner),
|
22
|
+
:size => opts(:workers, 1)) do |env|
|
23
|
+
app.call env
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(env)
|
28
|
+
queue.push env
|
29
|
+
response = uuidable.create_uuids env.message
|
30
|
+
response.kind = 'serf/messages/message_accepted_event'
|
31
|
+
response.message = env.message
|
32
|
+
response.context = env.context
|
33
|
+
return response
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
|
3
|
+
module Serf
|
4
|
+
module Middleware
|
5
|
+
|
6
|
+
##
|
7
|
+
# Middleware to turn an env into a Hashie::Mash.
|
8
|
+
#
|
9
|
+
class Masherize
|
10
|
+
|
11
|
+
##
|
12
|
+
# @param app the app
|
13
|
+
#
|
14
|
+
def initialize(app)
|
15
|
+
@app = app
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
@app.call Hashie::Mash.new(env)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -4,26 +4,32 @@ module Serf
|
|
4
4
|
module Middleware
|
5
5
|
|
6
6
|
##
|
7
|
-
# Middleware to add a request uuid
|
8
|
-
# the
|
7
|
+
# Middleware to add a request uuid to both the message and context
|
8
|
+
# of the env hash. But it won't overwrite the uuid field
|
9
9
|
# if the incoming request already has it.
|
10
10
|
#
|
11
11
|
class UuidTagger
|
12
|
+
include Serf::Util::OptionsExtraction
|
13
|
+
|
14
|
+
attr_reader :uuidable
|
12
15
|
|
13
16
|
##
|
14
17
|
# @param app the app
|
15
18
|
# @options opts [String] :field the ENV field to set with a UUID.
|
16
19
|
#
|
17
|
-
def initialize(app,
|
20
|
+
def initialize(app, *args)
|
21
|
+
extract_options! args
|
18
22
|
@app = app
|
19
|
-
@
|
23
|
+
@uuidable = opts :uuidable, Serf::Util::Uuidable
|
20
24
|
end
|
21
25
|
|
22
26
|
def call(env)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
message = env[:message]
|
28
|
+
message[:uuid] = uuidable.create_coded_uuid if message && !message[:uuid]
|
29
|
+
|
30
|
+
context = env[:context]
|
31
|
+
context[:uuid] = uuidable.create_coded_uuid if context && !context[:uuid]
|
32
|
+
|
27
33
|
@app.call env
|
28
34
|
end
|
29
35
|
|
@@ -1,5 +1,7 @@
|
|
1
|
+
require 'serf/util/options_extraction'
|
2
|
+
|
1
3
|
module Serf
|
2
|
-
module
|
4
|
+
module Routing
|
3
5
|
|
4
6
|
##
|
5
7
|
# A matcher that does a regexp match on a specific field
|
@@ -7,20 +9,24 @@ module Util
|
|
7
9
|
# on message kinds for routing.
|
8
10
|
#
|
9
11
|
class RegexpMatcher
|
12
|
+
include Serf::Util::OptionsExtraction
|
13
|
+
|
10
14
|
attr_reader :regexp
|
11
15
|
attr_reader :field
|
12
16
|
|
13
|
-
def initialize(regexp,
|
17
|
+
def initialize(regexp, *args)
|
18
|
+
extract_options! args
|
19
|
+
|
14
20
|
@regexp = regexp
|
15
|
-
@field =
|
21
|
+
@field = opts :field, :kind
|
16
22
|
end
|
17
23
|
|
18
24
|
def ===(env)
|
19
|
-
return
|
25
|
+
return regexp === env[field]
|
20
26
|
end
|
21
27
|
|
22
|
-
def self.build(
|
23
|
-
|
28
|
+
def self.build(*args, &block)
|
29
|
+
new *args, &block
|
24
30
|
end
|
25
31
|
|
26
32
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'serf/util/options_extraction'
|
2
|
+
|
3
|
+
module Serf
|
4
|
+
module Routing
|
5
|
+
|
6
|
+
class Route
|
7
|
+
include Serf::Util::OptionsExtraction
|
8
|
+
|
9
|
+
attr_reader :policies
|
10
|
+
attr_reader :command
|
11
|
+
|
12
|
+
def initialize(*args, &block)
|
13
|
+
extract_options! args
|
14
|
+
@policies = opts :policies, []
|
15
|
+
@command = opts! :command
|
16
|
+
end
|
17
|
+
|
18
|
+
def check_policies!(request, context)
|
19
|
+
for policy in policies do
|
20
|
+
policy.check! request, context
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def execute!(*args, &block)
|
25
|
+
command.call *args, &block
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.build(*args, &block)
|
29
|
+
new *args, &block
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'serf/routing/regexp_matcher'
|
2
|
+
require 'serf/util/options_extraction'
|
3
|
+
|
4
|
+
module Serf
|
5
|
+
module Routing
|
6
|
+
|
7
|
+
##
|
8
|
+
# RouteSet resolves a list of matched routes to execute based on
|
9
|
+
# criteria from associated 'matcher' objects.
|
10
|
+
#
|
11
|
+
class RouteSet
|
12
|
+
include Serf::Util::OptionsExtraction
|
13
|
+
|
14
|
+
attr_reader :routes
|
15
|
+
attr_reader :matchers
|
16
|
+
attr_reader :regexp_matcher_factory
|
17
|
+
|
18
|
+
def initialize(*args, &block)
|
19
|
+
extract_options! args
|
20
|
+
@routes = {}
|
21
|
+
@matchers = []
|
22
|
+
@regexp_matcher_factory = opts(
|
23
|
+
:regexp_matcher_factory,
|
24
|
+
::Serf::Routing::RegexpMatcher)
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# Connects a matcher (String or an Object implementing ===) to routes.
|
29
|
+
#
|
30
|
+
def add(matcher, route)
|
31
|
+
# Maybe we have an non-String matcher. Handle the Regexp case.
|
32
|
+
# We only keep track of matchers if it isn't a string because
|
33
|
+
# string matchers are just pulled out of routes by key lookup.
|
34
|
+
matcher = regexp_matcher_factory.build matcher if matcher.kind_of? Regexp
|
35
|
+
matchers << matcher unless matcher.is_a? String
|
36
|
+
|
37
|
+
# We add the (matcher+routes) into our routes
|
38
|
+
routes[matcher] ||= []
|
39
|
+
routes[matcher].push route
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# @param [Hash] request The input message to match for routes.
|
44
|
+
# @return [Array] List of routes that matched.
|
45
|
+
#
|
46
|
+
def resolve(request, context)
|
47
|
+
resolved_routes = []
|
48
|
+
resolved_routes.concat routes.fetch(request[:kind]) { [] }
|
49
|
+
matchers.each do |matcher|
|
50
|
+
resolved_routes.concat routes[matcher] if matcher === request
|
51
|
+
end
|
52
|
+
return resolved_routes
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Default factory method.
|
57
|
+
#
|
58
|
+
def self.build(*args, &block)
|
59
|
+
new *args, &block
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|