bug_bunny 3.0.6 → 3.1.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 +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +228 -4
- data/Rakefile +10 -6
- data/lib/bug_bunny/configuration.rb +13 -2
- data/lib/bug_bunny/consumer.rb +53 -25
- data/lib/bug_bunny/controller.rb +135 -91
- data/lib/bug_bunny/exception.rb +4 -0
- data/lib/bug_bunny/producer.rb +10 -3
- data/lib/bug_bunny/resource.rb +69 -150
- data/lib/bug_bunny/session.rb +65 -44
- data/lib/bug_bunny/version.rb +1 -1
- data/test/integration/fire_and_forget_test.rb +76 -0
- data/test/integration/rpc_flow_test.rb +78 -0
- data/test/test_helper.rb +24 -0
- data/test/unit/configuration_test.rb +40 -0
- data/test/unit/consumer_test.rb +44 -0
- data/test/unit/controller_headers_test.rb +38 -0
- data/test/unit/hybrid_resource_test.rb +60 -0
- data/test/unit/middleware_test.rb +61 -0
- data/test/unit/resource_test.rb +49 -0
- metadata +39 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b31963a71fce5b70886e19ee276f174d55052ffce7fdcd90b4b662e70ac6080
|
|
4
|
+
data.tar.gz: 851e107eec43ebe45ec3059d2148197e756299fdcf7a5efb5120e0856a2ca3b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 849447e5f3655f10f3bff047db0dc3ce5bbc2d6190a8db030ffe7b7a1d9619eebb9ac907c30f87e059d5e1c0b1dfce81f80b8f1768346f1fcee7b62f678b0243
|
|
7
|
+
data.tar.gz: bacfee7008aae385333d3b4e9139b0eeb89cdb1332259f19ec34491db71a1cb824004b968f54a699b7b0e35b94eca5ff4c134bb5ff04a7ffa095ab9d533408e3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
## [3.1.0] - 2026-02-18
|
|
3
|
+
|
|
4
|
+
### 🌟 New Features: Observability & Tracing
|
|
5
|
+
* **Distributed Tracing Stack:** Implemented a native distributed tracing system that ensures full visibility from the Producer to the Consumer/Worker.
|
|
6
|
+
* **Producer:** Messages now automatically carry a `correlation_id`. Added support for custom Middlewares to inject IDs from the application context (e.g., Rails `Current.request_id` or Sidekiq IDs).
|
|
7
|
+
* **Consumer:** Automatically extracts the `correlation_id` from AMQP headers and wraps the entire execution in a **Tagged Logger** block (e.g., `[d41d8cd9...] [API] Processing...`).
|
|
8
|
+
* **Controller:** Introduced `self.log_tags` to allow injecting rich business context into logs (e.g., `[Tenant-123]`) using the native `around_action` hook.
|
|
9
|
+
|
|
10
|
+
### 🛡 Security
|
|
11
|
+
* **Router Hardening:** Added a strict inheritance check in the `Consumer`.
|
|
12
|
+
* **Prevention:** The router now verifies that the instantiated class inherits from `BugBunny::Controller` before execution.
|
|
13
|
+
* **Impact:** Prevents potential **Remote Code Execution (RCE)** vulnerabilities where an attacker could try to instantiate arbitrary system classes (like `::Kernel`) via the `type` header.
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug Fixes
|
|
16
|
+
* **RPC Type Consistency:** Fixed a critical issue where RPC responses were ignored if the `correlation_id` was an Integer.
|
|
17
|
+
* **Fix:** The Producer now strictly normalizes all correlation IDs to Strings (`.to_s`) during both storage (pending requests) and retrieval (reply listener), ensuring reliable matching regardless of the ID format.
|
|
18
|
+
|
|
2
19
|
## [3.0.6] - 2026-02-17
|
|
3
20
|
|
|
4
21
|
### ♻️ Refactor & Standards
|
data/README.md
CHANGED
|
@@ -54,12 +54,24 @@ BugBunny.configure do |config|
|
|
|
54
54
|
|
|
55
55
|
# --- Logging (Niveles recomendados) ---
|
|
56
56
|
# Logger de BugBunny: Muestra tus requests (INFO)
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
rails_logger = Rails.logger
|
|
58
|
+
|
|
59
|
+
if defined?(ActiveSupport::TaggedLogging) && !rails_logger.respond_to?(:tagged)
|
|
60
|
+
config.logger = ActiveSupport::TaggedLogging.new(rails_logger)
|
|
61
|
+
else
|
|
62
|
+
config.logger = rails_logger
|
|
63
|
+
end
|
|
59
64
|
|
|
60
65
|
# Logger de Bunny (Driver): Silencia el ruido de bajo nivel (WARN)
|
|
61
|
-
|
|
66
|
+
if defined?(ActiveSupport::TaggedLogging) && !rails_logger.respond_to?(:tagged)
|
|
67
|
+
config.bunny_logger = ActiveSupport::TaggedLogging.new(rails_logger)
|
|
68
|
+
else
|
|
69
|
+
config.bunny_logger = rails_logger
|
|
70
|
+
end
|
|
62
71
|
config.bunny_logger.level = Logger::WARN
|
|
72
|
+
|
|
73
|
+
# Controller Namaspeace
|
|
74
|
+
config.controller_namespace = 'MyApp::AsyncHandlers' # Default: 'Rabbit::Controllers'
|
|
63
75
|
end
|
|
64
76
|
```
|
|
65
77
|
|
|
@@ -149,6 +161,80 @@ svc.save
|
|
|
149
161
|
# Log: [BugBunny] [POST] '/services' | Routing Key: 'urgent'
|
|
150
162
|
```
|
|
151
163
|
|
|
164
|
+
### Soporte de Parámetros Anidados (Nested Queries)
|
|
165
|
+
En la versión 3.0.3 arreglaste la serialización usando `Rack::Utils`. Esto es una "feature" poderosa que permite filtrar por hashes complejos, algo muy común en APIs modernas.
|
|
166
|
+
|
|
167
|
+
**Sugerencia:** Agregar un ejemplo en la sección **CRUD RESTful > LEER (GET)**:
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
# --- LEER CON FILTROS AVANZADOS ---
|
|
171
|
+
# Soporta hashes anidados (gracias a Rack::Utils)
|
|
172
|
+
# Envia: GET services?q[status]=active&q[tags][]=web
|
|
173
|
+
Manager::Service.where(q: { status: 'active', tags: ['web'] })
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 🔌 Manipulación de Headers (Middleware)
|
|
177
|
+
|
|
178
|
+
BugBunny permite interceptar y modificar las peticiones antes de que se envíen a RabbitMQ utilizando `client_middleware`. Esto es ideal para inyectar trazas, autenticación o metadatos de contexto.
|
|
179
|
+
|
|
180
|
+
Existen 3 formas principales de usarlo:
|
|
181
|
+
|
|
182
|
+
#### 1. Definición Inline (Rápida)
|
|
183
|
+
Ideal para inyectar headers estáticos específicos de un recurso.
|
|
184
|
+
```ruby
|
|
185
|
+
class Payment < BugBunny::Resource
|
|
186
|
+
client_middleware do |stack|
|
|
187
|
+
stack.use(Class.new(BugBunny::Middleware::Base) do
|
|
188
|
+
def on_request(env)
|
|
189
|
+
env.headers['X-Service-Version'] = 'v2'
|
|
190
|
+
env.headers['Content-Type'] = 'application/json'
|
|
191
|
+
end
|
|
192
|
+
end)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
#### 2. Clase Reutilizable (Recomendada)
|
|
198
|
+
Si tienes lógica compartida (ej: Autenticación), define una clase y úsala en múltiples recursos.
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
# app/middleware/auth_middleware.rb
|
|
202
|
+
class AuthMiddleware < BugBunny::Middleware::Base
|
|
203
|
+
def on_request(env)
|
|
204
|
+
env.headers['Authorization'] = "Bearer #{ENV['API_KEY']}"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# app/models/user.rb
|
|
209
|
+
class User < BugBunny::Resource
|
|
210
|
+
client_middleware do |stack|
|
|
211
|
+
stack.use AuthMiddleware
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### 3. Contexto Dinámico (Pro)
|
|
217
|
+
Permite inyectar valores que cambian en cada petición (como el Usuario actual o Tenant), leyendo de variables globales thread-safe (como CurrentAttributes en Rails).
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
# Middleware que lee el Tenant actual
|
|
221
|
+
# app/middleware/tenant_middleware.rb
|
|
222
|
+
class TenantMiddleware < BugBunny::Middleware::Base
|
|
223
|
+
def on_request(env)
|
|
224
|
+
# Ejemplo usando Rails CurrentAttributes
|
|
225
|
+
if Current.tenant_id
|
|
226
|
+
env.headers['X-Tenant-ID'] = Current.tenant_id
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
class Order < BugBunny::Resource
|
|
232
|
+
client_middleware do |stack|
|
|
233
|
+
stack.use TenantMiddleware
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
```
|
|
237
|
+
|
|
152
238
|
---
|
|
153
239
|
|
|
154
240
|
## 📡 Modo Servidor (Worker & Router)
|
|
@@ -217,7 +303,21 @@ class ApplicationController < BugBunny::Controller
|
|
|
217
303
|
end
|
|
218
304
|
```
|
|
219
305
|
|
|
220
|
-
### 3.
|
|
306
|
+
### 3. Namespace de Controladores (Opcional)
|
|
307
|
+
|
|
308
|
+
Por defecto, BugBunny busca los controladores dentro del módulo `Rabbit::Controllers`. Esto implica que tus archivos deben estar en `app/rabbit/controllers/`.
|
|
309
|
+
|
|
310
|
+
Si prefieres organizar tus consumidores en otro lugar (ej: dentro de un dominio específico o carpeta existente), puedes cambiar el namespace.
|
|
311
|
+
|
|
312
|
+
**Configuración:**
|
|
313
|
+
```ruby
|
|
314
|
+
# config/initializers/bug_bunny.rb
|
|
315
|
+
BugBunny.configure do |config|
|
|
316
|
+
config.controller_namespace = 'Billing::Events'
|
|
317
|
+
end
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### 4. Tabla de Ruteo (Convención)
|
|
221
321
|
|
|
222
322
|
El Router infiere la acción automáticamente:
|
|
223
323
|
|
|
@@ -230,6 +330,116 @@ El Router infiere la acción automáticamente:
|
|
|
230
330
|
| `DELETE` | `services/12` | `ServicesController` | `destroy` |
|
|
231
331
|
| `POST` | `services/12/restart` | `ServicesController` | `restart` (Custom) |
|
|
232
332
|
|
|
333
|
+
### 🔎 Observabilidad y Logging
|
|
334
|
+
|
|
335
|
+
BugBunny implementa un sistema de **Tracing Distribuido** nativo. Esto permite rastrear una petición desde que se origina en tu aplicación (Producer) hasta que es procesada por el worker (Consumer), manteniendo el mismo ID de traza (`correlation_id`) en todos los logs.
|
|
336
|
+
|
|
337
|
+
#### 1. Productor: Inyectar el Trace ID
|
|
338
|
+
|
|
339
|
+
Para asegurar que los mensajes salgan de tu aplicación con el ID de traza correcto (por ejemplo, el `X-Request-Id` de Rails, Sidekiq o tu propio `Current.request_id`), debes inyectarlo antes de publicar el mensaje.
|
|
340
|
+
|
|
341
|
+
La forma recomendada es crear un Middleware y registrarlo globalmente.
|
|
342
|
+
|
|
343
|
+
**A. Crear el Middleware**
|
|
344
|
+
|
|
345
|
+
```ruby
|
|
346
|
+
# app/middleware/correlation_injector.rb
|
|
347
|
+
class CorrelationInjector < BugBunny::Middleware::Base
|
|
348
|
+
def on_request(env)
|
|
349
|
+
# Ejemplo: Si usas Rails CurrentAttributes o similar
|
|
350
|
+
if defined?(Current) && Current.request_id
|
|
351
|
+
env.correlation_id = Current.request_id
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**B. Registrar el Middleware (Initializer)**
|
|
358
|
+
|
|
359
|
+
```ruby
|
|
360
|
+
# config/initializers/bug_bunny.rb
|
|
361
|
+
require 'bug_bunny'
|
|
362
|
+
require_relative '../../app/middleware/correlation_injector'
|
|
363
|
+
|
|
364
|
+
# Módulo para interceptar la inicialización de cualquier cliente
|
|
365
|
+
module BugBunnyGlobalMiddleware
|
|
366
|
+
def initialize(pool:)
|
|
367
|
+
super
|
|
368
|
+
@stack.use CorrelationInjector
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Aplicamos el parche para que afecte a Resources y Clientes manuales
|
|
373
|
+
BugBunny::Client.prepend(BugBunnyGlobalMiddleware)
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
#### 2. Consumidor: Logging Automático
|
|
379
|
+
|
|
380
|
+
El consumidor de BugBunny está diseñado para garantizar la trazabilidad "out-of-the-box".
|
|
381
|
+
|
|
382
|
+
##### A. Comportamiento por Defecto
|
|
383
|
+
Al recibir un mensaje, el Consumidor realiza automáticamente los siguientes pasos:
|
|
384
|
+
1. Extrae el `correlation_id` de las propiedades AMQP (o genera un UUID si no existe).
|
|
385
|
+
2. Envuelve todo el procesamiento en un bloque de log etiquetado (`tagged logging`).
|
|
386
|
+
3. Pasa el ID al Controlador.
|
|
387
|
+
|
|
388
|
+
**No necesitas configurar nada.** Tus logs se verán así automáticamente:
|
|
389
|
+
|
|
390
|
+
```text
|
|
391
|
+
[d41d8cd9-8f00...] [Consumer] Listening on queue...
|
|
392
|
+
[d41d8cd9-8f00...] [API] Procesando usuario 123...
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
##### B. Configuración Global (Initializer)
|
|
396
|
+
Si deseas agregar tags estáticos que aparezcan en **todos** los mensajes procesados por este worker (como el nombre del servicio, versión o entorno), agrégalos a `config.log_tags`.
|
|
397
|
+
|
|
398
|
+
> **Nota:** No agregues `:uuid` aquí, ya que el Consumidor lo agrega automáticamente.
|
|
399
|
+
|
|
400
|
+
```ruby
|
|
401
|
+
BugBunny.configure do |config|
|
|
402
|
+
# ... configuración de conexión ...
|
|
403
|
+
|
|
404
|
+
# Tags globales adicionales
|
|
405
|
+
config.log_tags = [
|
|
406
|
+
'WORKER',
|
|
407
|
+
->(_) { ENV['APP_VERSION'] }
|
|
408
|
+
]
|
|
409
|
+
end
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Resultado en Log:**
|
|
413
|
+
```text
|
|
414
|
+
[d41d8cd9...] [WORKER] [v1.0.2] [API] Procesando mensaje...
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
##### C. Configuración por Controlador (Contexto Rico)
|
|
418
|
+
Para agregar información específica del mensaje o lógica de negocio (como IDs de inquilinos, usuario actual, o headers específicos), utiliza `self.log_tags` en tus controladores.
|
|
419
|
+
|
|
420
|
+
Esto aprovecha el `around_action` nativo de la gema para inyectar contexto.
|
|
421
|
+
|
|
422
|
+
```ruby
|
|
423
|
+
# app/rabbit/controllers/application_controller.rb
|
|
424
|
+
module Rabbit
|
|
425
|
+
module Controllers
|
|
426
|
+
class ApplicationController < BugBunny::Controller
|
|
427
|
+
# Define tags dinámicos basados en el mensaje actual
|
|
428
|
+
self.log_tags = [
|
|
429
|
+
->(c) { c.params[:tenant_id] }, # Tag del Tenant (si viene en el body)
|
|
430
|
+
->(c) { c.headers['X-Source'] } # Tag del origen
|
|
431
|
+
]
|
|
432
|
+
end
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**Resultado Final en Log:**
|
|
438
|
+
(UUID Automático + Tag Global + Tag de Controlador)
|
|
439
|
+
```text
|
|
440
|
+
[d41d8cd9...] [WORKER] [Tenant-55] [Console] Creando usuario...
|
|
441
|
+
```
|
|
442
|
+
|
|
233
443
|
---
|
|
234
444
|
|
|
235
445
|
## 🔌 Modo Publisher (Cliente Manual)
|
|
@@ -256,6 +466,20 @@ client.publish('audit/events',
|
|
|
256
466
|
)
|
|
257
467
|
```
|
|
258
468
|
|
|
469
|
+
### ⚠️ Consideraciones sobre RPC (Direct Reply-To)
|
|
470
|
+
|
|
471
|
+
BugBunny utiliza el mecanismo nativo `amq.rabbitmq.reply-to` para las peticiones RPC. Esto maximiza el rendimiento eliminando la necesidad de crear colas temporales por cada petición.
|
|
472
|
+
|
|
473
|
+
**Trade-off:**
|
|
474
|
+
Al usar este mecanismo, las respuestas son efímeras. Si el proceso Cliente (tu aplicación Rails/Sidekiq) se reinicia abruptamente justo después de enviar la petición pero milisegundos antes de procesar la respuesta, **esa respuesta se perderá**.
|
|
475
|
+
|
|
476
|
+
**Recomendación:**
|
|
477
|
+
Diseña tus acciones de Controlador RPC (`POST`, `PUT`) para que sean **idempotentes**.
|
|
478
|
+
* *Mal diseño:* "Crear pago" (si se reintenta, cobra doble).
|
|
479
|
+
* *Buen diseño:* "Crear pago con ID X" (si se reintenta y ya existe, devuelve el recibo existente).
|
|
480
|
+
|
|
481
|
+
Esto permite que, ante un `BugBunny::RequestTimeout` por caída del cliente, puedas reintentar la operación de forma segura.
|
|
482
|
+
|
|
259
483
|
---
|
|
260
484
|
|
|
261
485
|
## 🏗 Arquitectura REST-over-AMQP
|
data/Rakefile
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
require 'bundler/gem_tasks'
|
|
2
|
+
require 'rake/testtask'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
|
5
|
+
t.libs << 'test'
|
|
6
|
+
t.libs << 'lib'
|
|
7
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
8
|
+
t.verbose = true
|
|
9
|
+
t.warning = false
|
|
10
|
+
end
|
|
5
11
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
task default: :rubocop
|
|
12
|
+
task default: :test
|
|
@@ -57,10 +57,16 @@ module BugBunny
|
|
|
57
57
|
# @return [Integer] Intervalo en segundos para verificar la salud de la cola.
|
|
58
58
|
attr_accessor :health_check_interval
|
|
59
59
|
|
|
60
|
+
# @return [String] Namespace base donde se buscarán los controladores (default: 'Rabbit::Controllers').
|
|
61
|
+
attr_accessor :controller_namespace
|
|
62
|
+
|
|
63
|
+
# @return [Array<Symbol, Proc, String>]
|
|
64
|
+
attr_accessor :log_tags
|
|
65
|
+
|
|
60
66
|
# Inicializa la configuración con valores por defecto seguros.
|
|
61
67
|
def initialize
|
|
62
|
-
@host = '127.0.0.1'
|
|
63
|
-
@port = 5672
|
|
68
|
+
@host = '127.0.0.1'
|
|
69
|
+
@port = 5672
|
|
64
70
|
@username = 'guest'
|
|
65
71
|
@password = 'guest'
|
|
66
72
|
@vhost = '/'
|
|
@@ -80,6 +86,11 @@ module BugBunny
|
|
|
80
86
|
@channel_prefetch = 1
|
|
81
87
|
@rpc_timeout = 10
|
|
82
88
|
@health_check_interval = 60
|
|
89
|
+
|
|
90
|
+
# Configuración por defecto para mantener compatibilidad
|
|
91
|
+
@controller_namespace = 'Rabbit::Controllers'
|
|
92
|
+
|
|
93
|
+
@log_tags = [:uuid]
|
|
83
94
|
end
|
|
84
95
|
|
|
85
96
|
# Construye la URL de conexión AMQP basada en los atributos configurados.
|
data/lib/bug_bunny/consumer.rb
CHANGED
|
@@ -65,7 +65,17 @@ module BugBunny
|
|
|
65
65
|
start_health_check(queue_name)
|
|
66
66
|
|
|
67
67
|
q.subscribe(manual_ack: true, block: block) do |delivery_info, properties, body|
|
|
68
|
-
|
|
68
|
+
trace_id = properties.correlation_id
|
|
69
|
+
|
|
70
|
+
logger = BugBunny.configuration.logger
|
|
71
|
+
|
|
72
|
+
if logger.respond_to?(:tagged)
|
|
73
|
+
logger.tagged(trace_id) { process_message(delivery_info, properties, body) }
|
|
74
|
+
elsif defined?(Rails) && Rails.logger.respond_to?(:tagged)
|
|
75
|
+
Rails.logger.tagged(trace_id) { process_message(delivery_info, properties, body) }
|
|
76
|
+
else
|
|
77
|
+
process_message(delivery_info, properties, body)
|
|
78
|
+
end
|
|
69
79
|
end
|
|
70
80
|
rescue StandardError => e
|
|
71
81
|
BugBunny.configuration.logger.error("[Consumer] Connection Error: #{e.message}. Retrying...")
|
|
@@ -84,20 +94,28 @@ module BugBunny
|
|
|
84
94
|
# @param body [String] El payload crudo del mensaje.
|
|
85
95
|
# @return [void]
|
|
86
96
|
def process_message(delivery_info, properties, body)
|
|
87
|
-
|
|
88
|
-
|
|
97
|
+
BugBunny.configuration.logger.debug("delivery_info: #{delivery_info}, properties: #{properties}, body: #{body}")
|
|
98
|
+
# 1. Recuperación Robusta del Path (Ruta)
|
|
99
|
+
path = properties.type
|
|
100
|
+
if path.nil? || path.empty?
|
|
101
|
+
path = properties.headers ? properties.headers['path'] : nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if path.nil? || path.empty?
|
|
105
|
+
BugBunny.configuration.logger.error("[Consumer] Missing 'type' or 'path' header. Message rejected.")
|
|
89
106
|
session.channel.reject(delivery_info.delivery_tag, false)
|
|
90
107
|
return
|
|
91
108
|
end
|
|
92
109
|
|
|
93
|
-
#
|
|
94
|
-
|
|
110
|
+
# 2. Recuperación Robusta del Verbo HTTP
|
|
111
|
+
headers_hash = properties.headers || {}
|
|
112
|
+
http_method = headers_hash['x-http-method'] || headers_hash['method'] || 'GET'
|
|
95
113
|
|
|
96
|
-
#
|
|
97
|
-
route_info = router_dispatch(http_method,
|
|
114
|
+
# 3. Router: Inferencia de Controlador y Acción
|
|
115
|
+
route_info = router_dispatch(http_method, path)
|
|
98
116
|
|
|
99
|
-
|
|
100
|
-
type:
|
|
117
|
+
request_metadata = {
|
|
118
|
+
type: path,
|
|
101
119
|
http_method: http_method,
|
|
102
120
|
controller: route_info[:controller],
|
|
103
121
|
action: route_info[:action],
|
|
@@ -106,32 +124,41 @@ module BugBunny
|
|
|
106
124
|
content_type: properties.content_type,
|
|
107
125
|
correlation_id: properties.correlation_id,
|
|
108
126
|
reply_to: properties.reply_to
|
|
109
|
-
}
|
|
127
|
+
}.merge(properties.headers)
|
|
128
|
+
|
|
129
|
+
# 4. Instanciación Dinámica del Controlador
|
|
130
|
+
# Utilizamos el namespace configurado en lugar de hardcodear "Rabbit::Controllers"
|
|
131
|
+
begin
|
|
132
|
+
namespace = BugBunny.configuration.controller_namespace
|
|
133
|
+
controller_name = route_info[:controller].camelize
|
|
110
134
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
controller_class_name = "rabbit/controllers/#{route_info[:controller]}".camelize
|
|
114
|
-
controller_class = controller_class_name.constantize
|
|
135
|
+
# Construcción: "Messaging::Handlers" + "::" + "Users"
|
|
136
|
+
controller_class_name = "#{namespace}::#{controller_name}"
|
|
115
137
|
|
|
116
|
-
|
|
117
|
-
response_payload = controller_class.call(headers: headers, body: body)
|
|
138
|
+
controller_class = controller_class_name.constantize
|
|
118
139
|
|
|
119
|
-
|
|
140
|
+
unless controller_class < BugBunny::Controller
|
|
141
|
+
raise BugBunny::SecurityError, "Class #{controller_class} is not a valid BugBunny Controller"
|
|
142
|
+
end
|
|
143
|
+
rescue NameError => _e
|
|
144
|
+
BugBunny.configuration.logger.error("[Consumer] Controller not found: #{controller_class_name}")
|
|
145
|
+
handle_fatal_error(properties, 404, "Not Found", "Controller #{controller_class_name} not found")
|
|
146
|
+
session.channel.reject(delivery_info.delivery_tag, false)
|
|
147
|
+
return
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# 5. Ejecución del Pipeline (Middleware + Acción)
|
|
151
|
+
response_payload = controller_class.call(headers: request_metadata, body: body)
|
|
152
|
+
|
|
153
|
+
# 6. Respuesta RPC
|
|
120
154
|
if properties.reply_to
|
|
121
155
|
reply(response_payload, properties.reply_to, properties.correlation_id)
|
|
122
156
|
end
|
|
123
157
|
|
|
124
|
-
#
|
|
158
|
+
# 7. Acknowledge
|
|
125
159
|
session.channel.ack(delivery_info.delivery_tag)
|
|
126
160
|
|
|
127
|
-
rescue NameError => e
|
|
128
|
-
# Error 501/404: El controlador o la acción no existen.
|
|
129
|
-
BugBunny.configuration.logger.error("[Consumer] Routing Error: #{e.message}")
|
|
130
|
-
handle_fatal_error(properties, 501, "Routing Error", e.message)
|
|
131
|
-
session.channel.reject(delivery_info.delivery_tag, false)
|
|
132
|
-
|
|
133
161
|
rescue StandardError => e
|
|
134
|
-
# Error 500: Crash interno de la aplicación.
|
|
135
162
|
BugBunny.configuration.logger.error("[Consumer] Execution Error: #{e.message}")
|
|
136
163
|
handle_fatal_error(properties, 500, "Internal Server Error", e.message)
|
|
137
164
|
session.channel.reject(delivery_info.delivery_tag, false)
|
|
@@ -189,6 +216,7 @@ module BugBunny
|
|
|
189
216
|
# @param correlation_id [String] ID para correlacionar la respuesta con la petición original.
|
|
190
217
|
# @return [void]
|
|
191
218
|
def reply(payload, reply_to, correlation_id)
|
|
219
|
+
BugBunny.configuration.logger.debug("[Consumer] 📤 Enviando REPLY a: #{reply_to} | ID: #{correlation_id}")
|
|
192
220
|
session.channel.default_exchange.publish(
|
|
193
221
|
payload.to_json,
|
|
194
222
|
routing_key: reply_to,
|