Class: AceMQ::Rails::Configuration
- Inherits:
-
Object
- Object
- AceMQ::Rails::Configuration
- Defined in:
- lib/acemq/rails/configuration.rb
Overview
Everything this integration can be told, in one object.
Two ways in, and they are not alternatives:
config/acemq.yml, read through Rails' ownconfig_for, which already understands per-environment sections, ashared:block and ERB. That is where a URL, a virtual host and a topology belong: they differ per environment and they are data.config.acemq.*inconfig/application.rbor an environment file, for the things that are decisions rather than data — a codec object, an interceptor, a telemetry reporter. None of those can be written in YAML without inventing a second configuration language.
The YAML is read first and the Ruby is applied over it. A file checked in
for every environment is the general statement; a line in
config/environments/test.rb is the specific one, and the specific one
wins. This is the opposite order from database.yml, which has no Ruby
half to disagree with, and the same order as config.active_job.queue_adapter
overriding whatever an adapter's own defaults were.
Defined Under Namespace
Classes: ConsumerDefaults
Instance Attribute Summary collapse
-
#client_name ⇒ Object
What is stamped on every message this application publishes, in
x-acemq-origin. -
#codec ⇒ Object
A codec instance, for anything that cannot be named — a composite, an encrypted one, a claim-check wrapper.
-
#connect_on_boot ⇒ Object
Whether opening the connection is attempted while the web server boots.
-
#connect_timeout ⇒ Object
Seconds to wait for the AMQP handshake.
-
#consumer ⇒ Object
Consumer defaults, overridable per consumer class.
-
#consumer_paths ⇒ Object
Extra directories to autoload consumers from.
-
#declare_topology_on_boot ⇒ Object
Whether to apply
topologywhen the consumer process starts. -
#format ⇒ Object
The codec name, resolved through
AceMQ::AMQP::Codecs.build: bytes, json, string, toml, xml, yaml. -
#heartbeat ⇒ Object
Heartbeat interval in seconds, or
:serverto take the broker's suggestion. -
#interceptors ⇒ Object
Interceptors applied to the process connection as it is opened.
-
#max_outstanding_publishes ⇒ Object
How many publishes may be waiting for a confirm at once.
-
#password ⇒ Object
Broker login, kept out of the URL so it stays out of the log line that reports a failed connection.
-
#publisher_confirms ⇒ Object
Whether a publish waits for the broker to confirm it.
-
#shutdown_timeout ⇒ Object
Seconds the consumer process spends draining before it gives up.
-
#telemetry ⇒ Object
A telemetry reporter: anything answering
count,observeandgauge. -
#tls ⇒ Object
An
AceMQ::AMQP::Security, for a private certificate authority or a client certificate. -
#token ⇒ Object
A token instead of a username and password, for brokers that take one (RabbitMQ with OAuth 2).
-
#topology ⇒ Object
Declared on boot of the consumer process, and by
rake acemq:topology. -
#transport_options ⇒ Object
Extra keyword arguments handed to
Bunny.new, for the handful of things this configuration does not name. -
#url ⇒ Object
The AMQP URL.
-
#username ⇒ Object
Broker login, kept out of the URL so it stays out of the log line that reports a failed connection.
-
#virtual_host ⇒ Object
The AMQP virtual host.
Instance Method Summary collapse
-
#assign(settings) ⇒ Configuration
Applies a hash read out of
config/acemq.yml. -
#connection_arguments ⇒ Array(String, Hash)
What AMQP::Connection.open is called with.
-
#initialize ⇒ Configuration
constructor
A new instance of Configuration.
-
#resolved_codec ⇒ #encode
The codec this configuration asks for.
-
#resolved_credentials ⇒ AceMQ::AMQP::Credentials?
The credentials this configuration asks for, or nil to leave the URL to speak for itself.
-
#resolved_url ⇒ String
The URL with
virtual_hostapplied.
Constructor Details
#initialize ⇒ Configuration
Returns a new instance of Configuration.
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/acemq/rails/configuration.rb', line 180 def initialize @url = "amqp://guest:guest@localhost:5672" @connect_timeout = 10 @heartbeat = :server @publisher_confirms = true @format = "json" @declare_topology_on_boot = true @shutdown_timeout = 20 @connect_on_boot = false @transport_options = {} @topology = {} @interceptors = [] @consumer_paths = [] @consumer = ConsumerDefaults.new end |
Instance Attribute Details
#client_name ⇒ Object
What is stamped on every message this application publishes, in
x-acemq-origin. Defaults to the Rails application's name and the
hostname, which is what makes a dead-lettered message traceable to a pod.
60 61 62 |
# File 'lib/acemq/rails/configuration.rb', line 60 def client_name @client_name end |
#codec ⇒ Object
A codec instance, for anything that cannot be named — a composite, an encrypted one, a claim-check wrapper.
112 113 114 |
# File 'lib/acemq/rails/configuration.rb', line 112 def codec @codec end |
#connect_on_boot ⇒ Object
Whether opening the connection is attempted while the web server boots. False by default, and that default is a decision: a broker that is down should not stop an application from serving the pages that do not need it. The connection is opened on first use instead.
167 168 169 |
# File 'lib/acemq/rails/configuration.rb', line 167 def connect_on_boot @connect_on_boot end |
#connect_timeout ⇒ Object
Seconds to wait for the AMQP handshake.
63 64 65 |
# File 'lib/acemq/rails/configuration.rb', line 63 def connect_timeout @connect_timeout end |
#consumer ⇒ Object
Consumer defaults, overridable per consumer class.
156 157 158 |
# File 'lib/acemq/rails/configuration.rb', line 156 def consumer @consumer end |
#consumer_paths ⇒ Object
Extra directories to autoload consumers from.
Empty by default and that is not an omission: Rails already autoloads
every direct subdirectory of app/, so a consumer in app/consumers
needs nothing here. This is for lib/consumers and the like.
178 179 180 |
# File 'lib/acemq/rails/configuration.rb', line 178 def consumer_paths @consumer_paths end |
#declare_topology_on_boot ⇒ Object
Whether to apply topology when the consumer process starts. True by
default: a consumer that starts before its queue exists reads nothing
and says nothing about why.
153 154 155 |
# File 'lib/acemq/rails/configuration.rb', line 153 def declare_topology_on_boot @declare_topology_on_boot end |
#format ⇒ Object
The codec name, resolved through AceMQ::AMQP::Codecs.build: bytes,
json, string, toml, xml, yaml. AceMQ::AMQP::Codecs.names is the list,
and a name that is not on it raises.
Protobuf and Avro are deliberately not names. Each needs something a
string cannot carry — a generated message class, a schema, a registry —
so each is built and handed over as an object in codec instead. See
docs/serialization.md.
108 109 110 |
# File 'lib/acemq/rails/configuration.rb', line 108 def format @format end |
#heartbeat ⇒ Object
Heartbeat interval in seconds, or :server to take the broker's
suggestion. Taking the broker's suggestion is right almost always.
67 68 69 |
# File 'lib/acemq/rails/configuration.rb', line 67 def heartbeat @heartbeat end |
#interceptors ⇒ Object
Interceptors applied to the process connection as it is opened.
An interceptor lives on a connection — intercept_publish and
intercept_consume are instance methods on
AMQP::Connection — so registering one from an initializer means
reaching for AceMQ::Rails.connection, and that opens the socket during
boot. It is the one thing connect_on_boot: false exists to avoid, and
it happens quietly: the application dials the broker on the way up and
nothing in the configuration says why. This list is applied on the way
out of Connection.open instead, so an interceptor costs nothing until
something publishes or consumes.
Each entry is registered on both sides. The library works out which
hooks an object actually answers to once, at registration, so an object
with only before_publish is a publish interceptor and nothing else —
which is exactly how the library's own
AceMQ::AMQP::Telemetry::OpenTelemetry#install registers itself. A bare
object is accepted as well as an array.
Ruby rather than YAML, because an interceptor is an object. Applied only
by AceMQ::Rails.connection: a connection handed in with
AceMQ::Rails.connection= is a test's own, and is left as it was given.
144 145 146 |
# File 'lib/acemq/rails/configuration.rb', line 144 def interceptors @interceptors end |
#max_outstanding_publishes ⇒ Object
How many publishes may be waiting for a confirm at once. This is the back pressure that stops a runaway loop from holding a million messages in this process's memory. 0.6.0 of the library added it; the default is the library's.
73 74 75 |
# File 'lib/acemq/rails/configuration.rb', line 73 def max_outstanding_publishes @max_outstanding_publishes end |
#password ⇒ Object
Broker login, kept out of the URL so it stays out of the log line that reports a failed connection. Either both or neither.
47 48 49 |
# File 'lib/acemq/rails/configuration.rb', line 47 def password @password end |
#publisher_confirms ⇒ Object
Whether a publish waits for the broker to confirm it. True, and the writer refuses anything else.
Confirms are not optional in the library and there is no keyword to ask
for a publish without them: the publishing channel calls
confirm_select the moment it is opened, and publish raises
AMQP::PublishError when nothing comes back. The setting stays
because it has been documented as one since 0.1.0, and it refuses rather
than being ignored — a line in a configuration file that reads as though
it turned durability off, and did nothing at all, is worse than no line.
85 86 87 |
# File 'lib/acemq/rails/configuration.rb', line 85 def publisher_confirms @publisher_confirms end |
#shutdown_timeout ⇒ Object
Seconds the consumer process spends draining before it gives up. Must be
comfortably shorter than whatever will kill the process —
terminationGracePeriodSeconds, TimeoutStopSec, docker stop -t.
161 162 163 |
# File 'lib/acemq/rails/configuration.rb', line 161 def shutdown_timeout @shutdown_timeout end |
#telemetry ⇒ Object
A telemetry reporter: anything answering count, observe and gauge.
Nil means the library's own no-op, which costs nothing.
120 121 122 |
# File 'lib/acemq/rails/configuration.rb', line 120 def telemetry @telemetry end |
#tls ⇒ Object
An AceMQ::AMQP::Security, for a private certificate authority or a
client certificate. Nil means "whatever the URL scheme implies".
116 117 118 |
# File 'lib/acemq/rails/configuration.rb', line 116 def tls @tls end |
#token ⇒ Object
A token instead of a username and password, for brokers that take one
(RabbitMQ with OAuth 2). Mutually exclusive with username.
51 52 53 |
# File 'lib/acemq/rails/configuration.rb', line 51 def token @token end |
#topology ⇒ Object
Declared on boot of the consumer process, and by rake acemq:topology.
A hash of exchanges, queues and bindings; see TopologyBuilder.
148 149 150 |
# File 'lib/acemq/rails/configuration.rb', line 148 def topology @topology end |
#transport_options ⇒ Object
Extra keyword arguments handed to Bunny.new, for the handful of
things this configuration does not name.
171 172 173 |
# File 'lib/acemq/rails/configuration.rb', line 171 def @transport_options end |
#url ⇒ Object
The AMQP URL. amqps:// is verified against the system trust store on
its own; a broker with a private certificate authority needs tls.
43 44 45 |
# File 'lib/acemq/rails/configuration.rb', line 43 def url @url end |
#username ⇒ Object
Broker login, kept out of the URL so it stays out of the log line that reports a failed connection. Either both or neither.
47 48 49 |
# File 'lib/acemq/rails/configuration.rb', line 47 def username @username end |
#virtual_host ⇒ Object
The AMQP virtual host. Nil leaves whatever is in the URL alone; a value replaces the URL's path, which is how a vhost is expressed in AMQP.
55 56 57 |
# File 'lib/acemq/rails/configuration.rb', line 55 def virtual_host @virtual_host end |
Instance Method Details
#assign(settings) ⇒ Configuration
Applies a hash read out of config/acemq.yml.
String keys, because that is what YAML gives, and dashes accepted
alongside underscores because max-outstanding-publishes is how the
same setting is spelled in the Spring starter's application.yaml and
somebody will copy it across.
An unknown key raises rather than being ignored. A typo in a configuration file that is silently dropped is how a production broker ends up with the default retry policy and nobody knows why.
273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/acemq/rails/configuration.rb', line 273 def assign(settings) return self if settings.nil? settings.each do |key, value| name = normalise(key) case name when "consumer" then @consumer.assign(symbolize(value)) when "topology" then @topology = value when "transport_options" then @transport_options = symbolize(value) else assign_one(name, value) end end self end |
#connection_arguments ⇒ Array(String, Hash)
What AMQP::Connection.open is called with.
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 |
# File 'lib/acemq/rails/configuration.rb', line 340 def connection_arguments = { codec: resolved_codec, origin: client_name, retry_policy: consumer.retry_policy, prefetch: consumer.prefetch, retry_threshold: consumer.broker_wait_threshold, telemetry: telemetry, heartbeat: heartbeat, connection_timeout: connect_timeout, security: tls, credentials: resolved_credentials } # Only when asked for. The library's default is its own business and # repeating it here would freeze it at whatever it was the day this was # written. if max_outstanding_publishes [:max_outstanding_publishes] = max_outstanding_publishes.to_i end .compact! [resolved_url, .merge()] end |
#resolved_codec ⇒ #encode
The codec this configuration asks for.
291 292 293 294 295 |
# File 'lib/acemq/rails/configuration.rb', line 291 def resolved_codec return AceMQ::AMQP::Codec.check!(codec) if codec AceMQ::AMQP::Codecs.build(format) end |
#resolved_credentials ⇒ AceMQ::AMQP::Credentials?
The credentials this configuration asks for, or nil to leave the URL to speak for itself.
301 302 303 304 305 306 307 |
# File 'lib/acemq/rails/configuration.rb', line 301 def resolved_credentials return AceMQ::AMQP::Credentials.token(token) if token return nil if username.nil? || username.to_s.empty? AceMQ::AMQP::Credentials.of(username: username, password: password.to_s) end |
#resolved_url ⇒ String
The URL with virtual_host applied.
A vhost is the path of an AMQP URL, and this is fiddlier than joining
two strings because "/" is a legal vhost name — it is RabbitMQ's default
— and also the separator. The value here is taken literally, as the
name of the vhost, and escaped whole: / becomes /%2F and
tenant/a becomes /tenant%2Fa, which is the vhost really called
"tenant/a" rather than a path with two segments.
CGI.escape with its +++ put back, because CGI.escape encodes a space
the way a form does and a URI path wants %20. A vhost with a space in
it is rare and is not a reason for this to be wrong.
323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/acemq/rails/configuration.rb', line 323 def resolved_url return url.to_s if virtual_host.nil? || virtual_host.to_s.empty? require "uri" require "cgi" parsed = URI.parse(url.to_s) parsed.path = "/#{CGI.escape(virtual_host.to_s).gsub("+", "%20")}" parsed.to_s rescue URI::InvalidURIError => e raise AceMQ::AMQP::ConfigurationError, "acemq: the url #{AceMQ::AMQP::Transport.redact(url.to_s)} cannot be " \ "parsed, so virtual_host cannot be applied to it: #{e.}" end |