startback-websocket 0.14.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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/README.md +13 -0
- data/Rakefile +18 -0
- data/lib/startback/audit/prometheus.rb +87 -0
- data/lib/startback/audit/shared.rb +17 -0
- data/lib/startback/audit/trailer.rb +129 -0
- data/lib/startback/audit.rb +3 -0
- data/lib/startback/caching/entity_cache.rb +157 -0
- data/lib/startback/caching/no_store.rb +28 -0
- data/lib/startback/caching/store.rb +34 -0
- data/lib/startback/context/h_factory.rb +43 -0
- data/lib/startback/context/middleware.rb +53 -0
- data/lib/startback/context.rb +122 -0
- data/lib/startback/errors.rb +197 -0
- data/lib/startback/event/agent.rb +84 -0
- data/lib/startback/event/bus/bunny/async.rb +162 -0
- data/lib/startback/event/bus/bunny.rb +1 -0
- data/lib/startback/event/bus/memory/async.rb +45 -0
- data/lib/startback/event/bus/memory/sync.rb +35 -0
- data/lib/startback/event/bus/memory.rb +2 -0
- data/lib/startback/event/bus.rb +100 -0
- data/lib/startback/event/engine.rb +94 -0
- data/lib/startback/event/ext/context.rb +5 -0
- data/lib/startback/event/ext/operation.rb +13 -0
- data/lib/startback/event.rb +47 -0
- data/lib/startback/ext/date_time.rb +9 -0
- data/lib/startback/ext/time.rb +9 -0
- data/lib/startback/ext.rb +2 -0
- data/lib/startback/model.rb +6 -0
- data/lib/startback/operation/error_operation.rb +19 -0
- data/lib/startback/operation/multi_operation.rb +28 -0
- data/lib/startback/operation.rb +78 -0
- data/lib/startback/services.rb +11 -0
- data/lib/startback/support/data_object.rb +71 -0
- data/lib/startback/support/env.rb +41 -0
- data/lib/startback/support/fake_logger.rb +18 -0
- data/lib/startback/support/hooks.rb +48 -0
- data/lib/startback/support/log_formatter.rb +34 -0
- data/lib/startback/support/logger.rb +34 -0
- data/lib/startback/support/operation_runner.rb +150 -0
- data/lib/startback/support/robustness.rb +157 -0
- data/lib/startback/support/transaction_manager.rb +25 -0
- data/lib/startback/support/transaction_policy.rb +33 -0
- data/lib/startback/support/world.rb +54 -0
- data/lib/startback/support.rb +26 -0
- data/lib/startback/version.rb +8 -0
- data/lib/startback/web/api.rb +99 -0
- data/lib/startback/web/auto_caching.rb +85 -0
- data/lib/startback/web/catch_all.rb +52 -0
- data/lib/startback/web/cors_headers.rb +80 -0
- data/lib/startback/web/health_check.rb +49 -0
- data/lib/startback/web/magic_assets/ng_html_transformer.rb +80 -0
- data/lib/startback/web/magic_assets/rake_tasks.rb +64 -0
- data/lib/startback/web/magic_assets.rb +98 -0
- data/lib/startback/web/middleware.rb +13 -0
- data/lib/startback/web/prometheus.rb +16 -0
- data/lib/startback/web/shield.rb +58 -0
- data/lib/startback.rb +43 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/unit/audit/test_prometheus.rb +72 -0
- data/spec/unit/audit/test_trailer.rb +105 -0
- data/spec/unit/caching/test_entity_cache.rb +136 -0
- data/spec/unit/context/test_abstraction_factory.rb +64 -0
- data/spec/unit/context/test_dup.rb +42 -0
- data/spec/unit/context/test_fork.rb +37 -0
- data/spec/unit/context/test_h_factory.rb +31 -0
- data/spec/unit/context/test_middleware.rb +45 -0
- data/spec/unit/context/test_with_world.rb +20 -0
- data/spec/unit/context/test_world.rb +17 -0
- data/spec/unit/event/bus/memory/test_async.rb +43 -0
- data/spec/unit/event/bus/memory/test_sync.rb +43 -0
- data/spec/unit/support/hooks/test_after_hook.rb +54 -0
- data/spec/unit/support/hooks/test_before_hook.rb +54 -0
- data/spec/unit/support/operation_runner/test_around_run.rb +156 -0
- data/spec/unit/support/operation_runner/test_before_after_call.rb +48 -0
- data/spec/unit/support/test_data_object.rb +156 -0
- data/spec/unit/support/test_env.rb +75 -0
- data/spec/unit/support/test_robusteness.rb +229 -0
- data/spec/unit/support/test_transaction_manager.rb +64 -0
- data/spec/unit/support/test_world.rb +72 -0
- data/spec/unit/test_event.rb +62 -0
- data/spec/unit/test_operation.rb +55 -0
- data/spec/unit/test_support.rb +40 -0
- data/spec/unit/web/fixtures/assets/app/hello.es6 +4 -0
- data/spec/unit/web/fixtures/assets/app/hello.html +1 -0
- data/spec/unit/web/fixtures/assets/index.es6 +1 -0
- data/spec/unit/web/test_api.rb +82 -0
- data/spec/unit/web/test_auto_caching.rb +81 -0
- data/spec/unit/web/test_catch_all.rb +77 -0
- data/spec/unit/web/test_cors_headers.rb +88 -0
- data/spec/unit/web/test_healthcheck.rb +59 -0
- data/spec/unit/web/test_magic_assets.rb +82 -0
- data/tasks/test.rake +14 -0
- metadata +237 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
module Startback
|
2
|
+
#
|
3
|
+
# Defines an execution context for Startback applications, and provides
|
4
|
+
# a cached factory for related abstractions (see `factor`), and an
|
5
|
+
# extensible world, statically and dynamically.
|
6
|
+
#
|
7
|
+
# In web application, an instance of a context can be set on the Rack
|
8
|
+
# environment, using Context::Middleware.
|
9
|
+
#
|
10
|
+
# This class SHOULD be subclassed for application required extensions
|
11
|
+
# to prevent touching the global Startback state itself.
|
12
|
+
#
|
13
|
+
# Also, for event handling in distributed architectures, a Context should
|
14
|
+
# be dumpable and reloadable to JSON. An `h` information contract if provided
|
15
|
+
# for that. Subclasses may contribute to the dumping and reloading process
|
16
|
+
# through the `h_dump` and `h_factory` methods
|
17
|
+
#
|
18
|
+
# module MyApp
|
19
|
+
# class Context < Startback::Context
|
20
|
+
#
|
21
|
+
# attr_accessor :foo
|
22
|
+
#
|
23
|
+
# h_dump do |h|
|
24
|
+
# h.merge!("foo" => foo)
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# h_factor do |c,h|
|
28
|
+
# c.foo = h["foo"]
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
class Context
|
35
|
+
attr_accessor :original_rack_env
|
36
|
+
|
37
|
+
# An error handler can be provided on the Context class. The latter
|
38
|
+
# MUST expose an API similar to ruby's Logger class. It can be a logger
|
39
|
+
# instance, simply.
|
40
|
+
#
|
41
|
+
# Fatal errors catched by Web::CatchAll are sent on `error_handler#fatal`
|
42
|
+
#
|
43
|
+
# Deprecated, use the logger below instead.
|
44
|
+
attr_accessor :error_handler
|
45
|
+
|
46
|
+
# A logger can be provided on the context, and will be used for everything
|
47
|
+
# related to logging, audit trailing and robustness. The logger receives
|
48
|
+
# object following the log & trail conventions of Startback, and must
|
49
|
+
# convert them to wathever log format is necessary.
|
50
|
+
attr_accessor :logger
|
51
|
+
|
52
|
+
require_relative 'context/h_factory'
|
53
|
+
extend(Context::HFactory)
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
super
|
57
|
+
yield(self) if block_given?
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_writer :_world
|
61
|
+
protected :_world=
|
62
|
+
|
63
|
+
def self.world(who, &block)
|
64
|
+
@_world ||= Support::World.new
|
65
|
+
@_world = @_world.factory(who, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.factor_world(context)
|
69
|
+
@_world ||= Support::World.new
|
70
|
+
@_world.with_scope(context)
|
71
|
+
end
|
72
|
+
|
73
|
+
def world
|
74
|
+
@_world ||= self.class.factor_world(self)
|
75
|
+
end
|
76
|
+
|
77
|
+
def with_world(world)
|
78
|
+
dup do |ctx|
|
79
|
+
ctx._world = self.world.with(world)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Factors an instance of `clazz`, which must be a Context-related
|
84
|
+
# abstraction (i.e. its constructor takes the context as last parameters).
|
85
|
+
#
|
86
|
+
# Factored abstractions are cached for a given context & arguments.
|
87
|
+
def factor(clazz, *args)
|
88
|
+
@factored ||= {}
|
89
|
+
key = args.empty? ? clazz : [clazz] + args
|
90
|
+
@factored[key] ||= clazz.new(*(args << self))
|
91
|
+
end
|
92
|
+
|
93
|
+
def clean_factored!
|
94
|
+
@factored = {}
|
95
|
+
end
|
96
|
+
private :clean_factored!
|
97
|
+
|
98
|
+
def to_h
|
99
|
+
self.class.h_dump!(self)
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_json(*args, &bl)
|
103
|
+
to_h.to_json(*args, &bl)
|
104
|
+
end
|
105
|
+
|
106
|
+
def fork(h = nil)
|
107
|
+
dup.tap{|duped|
|
108
|
+
self.class.h_factor!(duped, h) if h
|
109
|
+
yield(duped) if block_given?
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
def dup
|
114
|
+
super.tap{|c|
|
115
|
+
c.send(:clean_factored!)
|
116
|
+
yield(c) if block_given?
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
end # class Context
|
121
|
+
end # module Startback
|
122
|
+
require_relative 'context/middleware'
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module Startback
|
2
|
+
module Errors
|
3
|
+
|
4
|
+
class Error < StandardError
|
5
|
+
def initialize(message = nil, causes = nil)
|
6
|
+
super(message)
|
7
|
+
@causes = Array(causes)
|
8
|
+
end
|
9
|
+
attr_reader :causes
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def status(code = nil)
|
13
|
+
if code.nil?
|
14
|
+
@code || (superclass.respond_to?(:status) ? superclass.status : 500)
|
15
|
+
else
|
16
|
+
@code = code || @code
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def keep_error(keep = nil)
|
21
|
+
@keep_error = keep unless keep.nil?
|
22
|
+
@keep_error
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def message
|
27
|
+
msg = super
|
28
|
+
return msg unless msg == self.class.name
|
29
|
+
parts = self.class.name.split('::').last.gsub(/[A-Z]/){|x|
|
30
|
+
" #{x.downcase}"
|
31
|
+
}.strip.split(" ")
|
32
|
+
parts = parts[0...-1] unless self.class.keep_error
|
33
|
+
parts.join(" ").capitalize
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_causes?
|
37
|
+
causes && !causes.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def cause
|
41
|
+
causes&.first
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class BadRequestError < Error
|
46
|
+
status 400
|
47
|
+
end
|
48
|
+
|
49
|
+
class UnauthorizedError < BadRequestError
|
50
|
+
status 401
|
51
|
+
end
|
52
|
+
|
53
|
+
class ForbiddenError < BadRequestError
|
54
|
+
status 403
|
55
|
+
end
|
56
|
+
|
57
|
+
class NotFoundError < BadRequestError
|
58
|
+
status 404
|
59
|
+
end
|
60
|
+
|
61
|
+
class MethodNotAllowedError < BadRequestError
|
62
|
+
status 405
|
63
|
+
end
|
64
|
+
|
65
|
+
class NotAcceptableError < BadRequestError
|
66
|
+
status 406
|
67
|
+
end
|
68
|
+
|
69
|
+
class ConflictError < BadRequestError
|
70
|
+
status 409
|
71
|
+
end
|
72
|
+
|
73
|
+
class GoneError < BadRequestError
|
74
|
+
status 410
|
75
|
+
end
|
76
|
+
|
77
|
+
class PreconditionFailedError < BadRequestError
|
78
|
+
status 412
|
79
|
+
end
|
80
|
+
|
81
|
+
class UnsupportedMediaTypeError < BadRequestError
|
82
|
+
status 415
|
83
|
+
end
|
84
|
+
|
85
|
+
class ExpectationFailedError < BadRequestError
|
86
|
+
status 417
|
87
|
+
end
|
88
|
+
|
89
|
+
class LockedError < BadRequestError
|
90
|
+
status 423
|
91
|
+
end
|
92
|
+
|
93
|
+
class PreconditionRequiredError < BadRequestError
|
94
|
+
status 428
|
95
|
+
end
|
96
|
+
|
97
|
+
class InternalServerError < Error
|
98
|
+
status 500
|
99
|
+
keep_error(true)
|
100
|
+
end
|
101
|
+
|
102
|
+
class NotImplementedError < InternalServerError
|
103
|
+
status 501
|
104
|
+
end
|
105
|
+
|
106
|
+
### Helper methods
|
107
|
+
|
108
|
+
def bad_request_error!(msg = nil)
|
109
|
+
raise Startback::Errors::BadRequestError, msg
|
110
|
+
end
|
111
|
+
module_function :bad_request_error!
|
112
|
+
|
113
|
+
def unauthorized_error!(msg = nil)
|
114
|
+
raise Startback::Errors::UnauthorizedError, msg
|
115
|
+
end
|
116
|
+
module_function :unauthorized_error!
|
117
|
+
|
118
|
+
def forbidden_error!(msg = nil)
|
119
|
+
raise Startback::Errors::ForbiddenError, msg
|
120
|
+
end
|
121
|
+
module_function :forbidden_error!
|
122
|
+
|
123
|
+
def not_found_error!(msg = nil)
|
124
|
+
raise Startback::Errors::NotFoundError, "#{msg} not found"
|
125
|
+
end
|
126
|
+
module_function :not_found_error!
|
127
|
+
|
128
|
+
def method_not_allowed_error!(msg = nil)
|
129
|
+
raise Startback::Errors::MethodNotAllowedError, msg
|
130
|
+
end
|
131
|
+
module_function :method_not_allowed_error!
|
132
|
+
|
133
|
+
def not_acceptable_error!(msg = nil)
|
134
|
+
raise Startback::Errors::NotAcceptableError, msg
|
135
|
+
end
|
136
|
+
module_function :not_acceptable_error!
|
137
|
+
|
138
|
+
def conflict_error!(msg = nil)
|
139
|
+
raise Startback::Errors::ConflictError, msg
|
140
|
+
end
|
141
|
+
module_function :conflict_error!
|
142
|
+
|
143
|
+
def gone_error!(msg = nil)
|
144
|
+
raise Startback::Errors::GoneError, msg
|
145
|
+
end
|
146
|
+
module_function :gone_error!
|
147
|
+
|
148
|
+
def precondition_failed_error!(msg = nil)
|
149
|
+
raise Startback::Errors::PreconditionFailedError, msg
|
150
|
+
end
|
151
|
+
module_function :precondition_failed_error!
|
152
|
+
|
153
|
+
def unsupported_media_type_error!(media)
|
154
|
+
raise Startback::Errors::UnsupportedMediaTypeError, "Unable to use `#{media}` as input data"
|
155
|
+
end
|
156
|
+
module_function :unsupported_media_type_error!
|
157
|
+
|
158
|
+
def expectation_failed_error!(msg = nil)
|
159
|
+
raise Startback::Errors::ExpectationFailedError, msg
|
160
|
+
end
|
161
|
+
module_function :expectation_failed_error!
|
162
|
+
|
163
|
+
def locked_error!(msg = nil)
|
164
|
+
raise Startback::Errors::LockedError, msg
|
165
|
+
end
|
166
|
+
module_function :locked_error!
|
167
|
+
|
168
|
+
def precondition_required_error!(msg = nil)
|
169
|
+
raise Startback::Errors::PreconditionRequiredError, msg
|
170
|
+
end
|
171
|
+
module_function :precondition_required_error!
|
172
|
+
|
173
|
+
def internal_server_error!(msg = nil)
|
174
|
+
raise Startback::Errors::InternalServerError, msg
|
175
|
+
end
|
176
|
+
module_function :internal_server_error!
|
177
|
+
|
178
|
+
def not_implemented_error!(msg = nil)
|
179
|
+
raise Startback::Errors::NotImplementedError, msg
|
180
|
+
end
|
181
|
+
module_function :not_implemented_error!
|
182
|
+
|
183
|
+
# Aliases
|
184
|
+
|
185
|
+
def user_error!(msg = nil)
|
186
|
+
raise Startback::Errors::BadRequestError, msg
|
187
|
+
end
|
188
|
+
module_function :user_error!
|
189
|
+
|
190
|
+
def server_error!(msg = nil)
|
191
|
+
raise Startback::Errors::InternalServerError, msg
|
192
|
+
end
|
193
|
+
module_function :server_error!
|
194
|
+
|
195
|
+
end # module Errors
|
196
|
+
include Errors
|
197
|
+
end # module Startback
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Startback
|
2
|
+
class Event
|
3
|
+
#
|
4
|
+
# An agent listen to specific events and react with its
|
5
|
+
# `call` method.
|
6
|
+
#
|
7
|
+
# This class is intended to be subclasses and the following
|
8
|
+
# methods overriden:
|
9
|
+
#
|
10
|
+
# - install_listeners that installs sync and async listeners
|
11
|
+
# - call to create a context and implement reaction behavior
|
12
|
+
#
|
13
|
+
class Agent
|
14
|
+
include Support::OperationRunner
|
15
|
+
include Support::Robustness
|
16
|
+
|
17
|
+
def initialize(engine)
|
18
|
+
@engine = engine
|
19
|
+
@context = nil
|
20
|
+
install_listeners
|
21
|
+
end
|
22
|
+
attr_reader :engine
|
23
|
+
attr_accessor :context
|
24
|
+
protected :context=
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
# Installs the various event handlers by calling `sync`
|
29
|
+
# and `async` methods.
|
30
|
+
#
|
31
|
+
# This method is intended to be overriden.
|
32
|
+
def install_listeners
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the underlying bus
|
36
|
+
def bus
|
37
|
+
engine.bus
|
38
|
+
end
|
39
|
+
|
40
|
+
# Asynchronously listen to a specific event.
|
41
|
+
#
|
42
|
+
# See Bus#listen
|
43
|
+
def async(exchange, queue)
|
44
|
+
bus.async.listen(exchange, queue) do |event_data|
|
45
|
+
event = engine.factor_event(event_data)
|
46
|
+
with_context(event.context).call(event)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Synchronously listen to a specific event.
|
51
|
+
#
|
52
|
+
# See Bus#listen
|
53
|
+
def sync(exchange, queue)
|
54
|
+
bus.listen(exchange, queue) do |event_data|
|
55
|
+
event = engine.factor_event(event_data)
|
56
|
+
with_context(event.context).call(event)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Reacts to a specific event.
|
61
|
+
#
|
62
|
+
# This method must be implemented by subclasses and raises
|
63
|
+
# an error by default.
|
64
|
+
def call(event = nil)
|
65
|
+
log(:fatal, {
|
66
|
+
op: self.class,
|
67
|
+
op_data: event,
|
68
|
+
error: %Q{Unexpected call to Startback::Event::Agent#call},
|
69
|
+
backtrace: caller
|
70
|
+
})
|
71
|
+
raise NotImplementedError
|
72
|
+
end
|
73
|
+
|
74
|
+
def with_context(context)
|
75
|
+
dup.tap{|a| a.send(:context=, context) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def operation_world(op)
|
79
|
+
super(op).merge(context: context)
|
80
|
+
end
|
81
|
+
|
82
|
+
end # class Agent
|
83
|
+
end # class Event
|
84
|
+
end # module Starback
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
module Startback
|
3
|
+
class Event
|
4
|
+
class Bus
|
5
|
+
module Bunny
|
6
|
+
#
|
7
|
+
# Asynchronous implementation of the bus abstraction, on top of RabbitMQ
|
8
|
+
# and using the 'bunny' gem (you need to include it in your Gemfile
|
9
|
+
# yourself: it is NOT a startback official dependency).
|
10
|
+
#
|
11
|
+
# This bus implementation emits events by dumping them to RabbitMQ using
|
12
|
+
# the event type as exchange name. Listeners may use the `processor`
|
13
|
+
# parameter to specify the queue name ; otherwise a default "main" queue
|
14
|
+
# is used.
|
15
|
+
#
|
16
|
+
# Examples:
|
17
|
+
#
|
18
|
+
# # Connects to RabbitMQ using all default options
|
19
|
+
# #
|
20
|
+
# # Uses the STARTBACK_BUS_BUNNY_ASYNC_URL environment variable for
|
21
|
+
# # connection URL if present.
|
22
|
+
# Startback::Bus::Bunny::Async.new
|
23
|
+
#
|
24
|
+
# # Connects to RabbitMQ using a specific URL
|
25
|
+
# Startback::Bus::Bunny::Async.new("amqp://rabbituser:rabbitpass@192.168.17.17")
|
26
|
+
# Startback::Bus::Bunny::Async.new(url: "amqp://rabbituser:rabbitpass@192.168.17.17")
|
27
|
+
#
|
28
|
+
# # Connects to RabbitMQ using specific connection options. See Bunny's own
|
29
|
+
# # documentation
|
30
|
+
# Startback::Bus::Bunny::Async.new({
|
31
|
+
# connection_options: {
|
32
|
+
# host: "192.168.17.17"
|
33
|
+
# }
|
34
|
+
# })
|
35
|
+
#
|
36
|
+
class Async
|
37
|
+
include Support::Robustness
|
38
|
+
|
39
|
+
CHANNEL_KEY = 'Startback::Bus::Bunny::Async::ChannelKey'
|
40
|
+
|
41
|
+
DEFAULT_OPTIONS = {
|
42
|
+
# (optional) The URL to use for connecting to RabbitMQ.
|
43
|
+
url: ENV['STARTBACK_BUS_BUNNY_ASYNC_URL'],
|
44
|
+
|
45
|
+
# (optional) The options has to pass to ::Bunny constructor
|
46
|
+
connection_options: nil,
|
47
|
+
|
48
|
+
# (optional) The options to use for the emitter/listener fanout
|
49
|
+
fanout_options: {},
|
50
|
+
|
51
|
+
# (optional) The options to use for the listener queue
|
52
|
+
queue_options: {},
|
53
|
+
|
54
|
+
# (optional) Default event factory to use, if any
|
55
|
+
event_factory: nil,
|
56
|
+
|
57
|
+
# (optional) A default context to use for general logging
|
58
|
+
context: nil,
|
59
|
+
|
60
|
+
# (optional) Size of consumer pool
|
61
|
+
consumer_pool_size: 1,
|
62
|
+
|
63
|
+
# (optional) Whether the program must be aborted on consumption
|
64
|
+
# error
|
65
|
+
abort_on_exception: true,
|
66
|
+
|
67
|
+
# (optional) Whether connection occurs immediately,
|
68
|
+
# or on demand later
|
69
|
+
autoconnect: false
|
70
|
+
}
|
71
|
+
|
72
|
+
# Creates a bus instance, using the various options provided to
|
73
|
+
# fine-tune behavior.
|
74
|
+
def initialize(options = {})
|
75
|
+
options = { url: options } if options.is_a?(String)
|
76
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
77
|
+
connect if @options[:autoconnect]
|
78
|
+
end
|
79
|
+
attr_reader :options
|
80
|
+
|
81
|
+
def connect
|
82
|
+
disconnect
|
83
|
+
conn = options[:connection_options] || options[:url]
|
84
|
+
try_max_times(10) do
|
85
|
+
@bunny = ::Bunny.new(conn)
|
86
|
+
@bunny.start
|
87
|
+
channel # make sure we already create the channel
|
88
|
+
log(:info, {op: "#{self.class.name}#connect", op_data: conn}, options[:context])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def disconnect
|
93
|
+
if channel = Thread.current[CHANNEL_KEY]
|
94
|
+
channel.close
|
95
|
+
Thread.current[CHANNEL_KEY] = nil
|
96
|
+
end
|
97
|
+
@bunny.close if @bunny
|
98
|
+
end
|
99
|
+
|
100
|
+
def channel
|
101
|
+
unless @bunny
|
102
|
+
raise Startback::Errors::Error, "Please connect your bus first, or use autoconnect: true"
|
103
|
+
end
|
104
|
+
|
105
|
+
Thread.current[CHANNEL_KEY] ||= @bunny.create_channel(
|
106
|
+
nil,
|
107
|
+
consumer_pool_size, # consumer_pool_size
|
108
|
+
abort_on_exception? # consumer_pool_abort_on_exception
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def emit(event)
|
113
|
+
stop_errors(self, "emit", event.context) do
|
114
|
+
fanout = channel.fanout(event.type.to_s, fanout_options)
|
115
|
+
fanout.publish(event.to_json)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def listen(type, processor = nil, listener = nil, &bl)
|
120
|
+
raise ArgumentError, "A listener must be provided" unless listener || bl
|
121
|
+
|
122
|
+
fanout = channel.fanout(type.to_s, fanout_options)
|
123
|
+
queue = channel.queue((processor || "main").to_s, queue_options)
|
124
|
+
queue.bind(fanout)
|
125
|
+
queue.subscribe do |delivery_info, properties, body|
|
126
|
+
stop_errors(self, "listen") do
|
127
|
+
(listener || bl).call(body)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
protected
|
133
|
+
|
134
|
+
def consumer_pool_size
|
135
|
+
options[:consumer_pool_size]
|
136
|
+
end
|
137
|
+
|
138
|
+
def abort_on_exception?
|
139
|
+
options[:abort_on_exception]
|
140
|
+
end
|
141
|
+
|
142
|
+
def fanout_options
|
143
|
+
options[:fanout_options]
|
144
|
+
end
|
145
|
+
|
146
|
+
def queue_options
|
147
|
+
options[:queue_options]
|
148
|
+
end
|
149
|
+
|
150
|
+
def factor_event(body)
|
151
|
+
if options[:event_factory]
|
152
|
+
options[:event_factory].call(body)
|
153
|
+
else
|
154
|
+
Event.json(body, options)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end # class Async
|
159
|
+
end # module Bunny
|
160
|
+
end # class Bus
|
161
|
+
end # class Event
|
162
|
+
end # module Startback
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'bunny/async'
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Startback
|
2
|
+
class Event
|
3
|
+
class Bus
|
4
|
+
module Memory
|
5
|
+
#
|
6
|
+
# Asynchronous implementation of the Bus abstraction, for use between
|
7
|
+
# components sharing the same process.
|
8
|
+
#
|
9
|
+
# This implementation actually calls listeners synchronously (it mays)
|
10
|
+
# but hides error raised by them. See Bus::Bunny::Async for another
|
11
|
+
# implementation that is truly asynchronous and relies on RabbitMQ.
|
12
|
+
#
|
13
|
+
class Async
|
14
|
+
include Support::Robustness
|
15
|
+
|
16
|
+
DEFAULT_OPTIONS = {
|
17
|
+
}
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
21
|
+
@listeners = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def connect
|
25
|
+
end
|
26
|
+
|
27
|
+
def emit(event)
|
28
|
+
(@listeners[event.type.to_s] || []).each do |l|
|
29
|
+
stop_errors(self, "emit", event) {
|
30
|
+
l.call(event)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def listen(type, processor = nil, listener = nil, &bl)
|
36
|
+
raise ArgumentError, "A listener must be provided" unless listener || bl
|
37
|
+
@listeners[type.to_s] ||= []
|
38
|
+
@listeners[type.to_s] << (listener || bl)
|
39
|
+
end
|
40
|
+
|
41
|
+
end # class Sync
|
42
|
+
end # module Memory
|
43
|
+
end # class Bus
|
44
|
+
end # class Event
|
45
|
+
end # module Startback
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Startback
|
2
|
+
class Event
|
3
|
+
class Bus
|
4
|
+
module Memory
|
5
|
+
#
|
6
|
+
# Synchronous implementation of the Bus abstraction, for use between
|
7
|
+
# components sharing the same process.
|
8
|
+
#
|
9
|
+
class Sync
|
10
|
+
include Support::Robustness
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@listeners = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def connect
|
17
|
+
end
|
18
|
+
|
19
|
+
def emit(event)
|
20
|
+
(@listeners[event.type.to_s] || []).each do |l|
|
21
|
+
l.call(event)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def listen(type, processor = nil, listener = nil, &bl)
|
26
|
+
raise ArgumentError, "A listener must be provided" unless listener || bl
|
27
|
+
@listeners[type.to_s] ||= []
|
28
|
+
@listeners[type.to_s] << (listener || bl)
|
29
|
+
end
|
30
|
+
|
31
|
+
end # class Sync
|
32
|
+
end # module Memory
|
33
|
+
end # class Bus
|
34
|
+
end # class Event
|
35
|
+
end # module Startback
|