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