Skip to content

Commit d9bca3a

Browse files
authored
feat: add fire-and-forget dispatcher plugin (#59)
New dispatcher that forwards requests to a configured upstream URL (best-effort) and returns an immediate static response. Useful for webhook ingestion, async job submission, and audit trails.
1 parent fc6c881 commit d9bca3a

11 files changed

Lines changed: 883 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- **plugin**: `fire-and-forget` dispatcher — forwards request to upstream (best-effort) and returns an immediate static response, useful for webhook ingestion, async job submission, and audit trails
12+
1013
## [0.5.2] - 2026-03-25
1114

1215
### Added

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ The playground includes a Train Travel API demo with WireMock backend, full obse
8686
| `s3` | Dispatcher | Proxy requests to AWS S3 / S3-compatible storage with SigV4 signing |
8787
| `ai-proxy` | Dispatcher | Unified LLM routing to OpenAI, Anthropic, and Ollama with provider fallback |
8888
| `ws-upstream` | Dispatcher | WebSocket transparent proxy with full middleware chain on upgrade |
89+
| `fire-and-forget` | Dispatcher | Forward request to upstream and return immediate static response |
8990
| `jwt-auth` | Middleware | JWT token validation |
9091
| `apikey-auth` | Middleware | API key authentication |
9192
| `basic-auth` | Middleware | HTTP Basic authentication (RFC 7617) |

crates/barbacane-test/tests/compilation.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@ async fn test_fixture_compiles_correlation_id() {
8787
assert_eq!(resp.status(), 200);
8888
}
8989

90+
#[tokio::test]
91+
async fn test_fixture_compiles_fire_and_forget() {
92+
let gateway = TestGateway::from_spec(&fixture("fire-and-forget.yaml"))
93+
.await
94+
.expect("fire-and-forget fixture failed to compile");
95+
let resp = gateway.get("/__barbacane/health").await.unwrap();
96+
assert_eq!(resp.status(), 200);
97+
}
98+
9099
#[tokio::test]
91100
async fn test_fixture_compiles_ai_proxy() {
92101
let gateway = TestGateway::from_spec(&fixture("ai-proxy.yaml"))

docs/guide/dispatchers.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,99 @@ Client: GET /ws/echo?token=abc → Upstream: ws://echo.internal:8080/?token=abc
10501050
- **Development mode**: `ws://` URLs are allowed with `--allow-plaintext-upstream`
10511051
- **Authentication**: Apply auth middleware (jwt-auth, oidc-auth, etc.) to protect WebSocket endpoints — middleware runs on the initial upgrade request
10521052

1053+
### fire-and-forget
1054+
1055+
Forwards the incoming request to a configured upstream URL without waiting for the result, and returns an immediate static response. Useful for webhook ingestion, async job submission, and audit trails.
1056+
1057+
The outbound HTTP call is best-effort: if the upstream is unreachable or returns an error, the client still receives the configured response.
1058+
1059+
```yaml
1060+
x-barbacane-dispatch:
1061+
name: fire-and-forget
1062+
config:
1063+
url: "http://backend:3000/ingest"
1064+
response:
1065+
status: 202
1066+
body: '{"accepted": true}'
1067+
```
1068+
1069+
#### Configuration
1070+
1071+
| Property | Type | Required | Default | Description |
1072+
|----------|------|----------|---------|-------------|
1073+
| `url` | string | Yes | - | Upstream URL to forward the request to |
1074+
| `timeout_ms` | integer | No | 5000 | Timeout in milliseconds for the upstream HTTP call |
1075+
| `response` | object | No | See below | Static response returned to the client |
1076+
1077+
##### Response Object
1078+
1079+
| Property | Type | Required | Default | Description |
1080+
|----------|------|----------|---------|-------------|
1081+
| `status` | integer | No | 202 | HTTP status code |
1082+
| `content_type` | string | No | `application/json` | Content-Type header value |
1083+
| `body` | string | No | `""` | Response body |
1084+
| `headers` | object | No | `{}` | Additional response headers |
1085+
1086+
#### How It Works
1087+
1088+
1. The request arrives (already processed by the middleware chain)
1089+
2. The dispatcher forwards method, headers, and body to the configured `url`
1090+
3. The upstream response is discarded — errors are logged as warnings
1091+
4. The configured static response is returned to the client
1092+
1093+
Because the upstream call happens synchronously in the WASM runtime, the client does wait for the HTTP call to complete (or time out). For truly decoupled async processing, consider using the `kafka` or `nats` dispatchers with a consumer behind them.
1094+
1095+
#### Examples
1096+
1097+
**Webhook ingestion:**
1098+
```yaml
1099+
/webhooks/stripe:
1100+
post:
1101+
x-barbacane-dispatch:
1102+
name: fire-and-forget
1103+
config:
1104+
url: "https://processor.internal/stripe-events"
1105+
timeout_ms: 3000
1106+
response:
1107+
status: 202
1108+
body: '{"received": true}'
1109+
```
1110+
1111+
**Audit logging:**
1112+
```yaml
1113+
/audit/events:
1114+
post:
1115+
x-barbacane-dispatch:
1116+
name: fire-and-forget
1117+
config:
1118+
url: "http://audit-service:9090/log"
1119+
response:
1120+
status: 200
1121+
body: '{"logged": true}'
1122+
headers:
1123+
X-Audit-Status: accepted
1124+
```
1125+
1126+
**Minimal config (defaults to 202, empty body):**
1127+
```yaml
1128+
/notify:
1129+
post:
1130+
x-barbacane-dispatch:
1131+
name: fire-and-forget
1132+
config:
1133+
url: "https://notification-service.internal/send"
1134+
```
1135+
1136+
#### Error Handling
1137+
1138+
The dispatcher never returns an error to the client. Upstream failures are logged but the configured static response is always returned.
1139+
1140+
| Upstream Outcome | Client Receives | Log Level |
1141+
|------------------|----------------|-----------|
1142+
| Success | Configured response | DEBUG |
1143+
| Connection error | Configured response | WARN |
1144+
| Timeout | Configured response | WARN |
1145+
10531146
---
10541147

10551148
## Best Practices

docs/reference/extensions.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,23 @@ paths:
143143
secret_access_key: env://AWS_SECRET_ACCESS_KEY
144144
```
145145

146+
### Dispatcher: `fire-and-forget`
147+
148+
Forwards request to upstream (best-effort) and returns an immediate static response.
149+
150+
```yaml
151+
x-barbacane-dispatch:
152+
name: fire-and-forget
153+
config:
154+
url: string # Required. Upstream URL to forward the request to
155+
timeout_ms: integer # Optional. Timeout in milliseconds (default: 5000)
156+
response: # Optional. Static response returned to the client
157+
status: integer # HTTP status code (default: 202)
158+
content_type: string # Content-Type header (default: "application/json")
159+
body: string # Response body (default: "")
160+
headers: object # Additional response headers (default: {})
161+
```
162+
146163
### Dispatcher: `ws-upstream`
147164

148165
Transparent WebSocket proxy. Upgrades the connection and relays frames bidirectionally.

plugins/fire-and-forget/Cargo.lock

Lines changed: 131 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/fire-and-forget/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "barbacane-fire-and-forget-dispatcher"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "Fire-and-forget dispatcher plugin for Barbacane API gateway"
6+
license = "AGPL-3.0-only"
7+
8+
# Mark as standalone crate (not part of any workspace)
9+
[workspace]
10+
11+
[lib]
12+
crate-type = ["cdylib", "rlib"]
13+
14+
[dependencies]
15+
barbacane-plugin-sdk = { path = "../../crates/barbacane-plugin-sdk" }
16+
serde = { version = "1", features = ["derive"] }
17+
serde_json = "1"
18+
19+
[profile.release]
20+
opt-level = "s"
21+
lto = true
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "urn:barbacane:plugin:fire-and-forget:config",
4+
"title": "Fire-and-Forget Dispatcher Config",
5+
"description": "Configuration for the fire-and-forget dispatcher plugin. Forwards the request to an upstream URL and returns an immediate static response.",
6+
"type": "object",
7+
"properties": {
8+
"url": {
9+
"type": "string",
10+
"description": "Upstream URL to forward the request to",
11+
"format": "uri"
12+
},
13+
"timeout_ms": {
14+
"type": "integer",
15+
"description": "Timeout in milliseconds for the upstream HTTP call",
16+
"default": 5000,
17+
"minimum": 1
18+
},
19+
"response": {
20+
"type": "object",
21+
"description": "Static response returned to the client",
22+
"properties": {
23+
"status": {
24+
"type": "integer",
25+
"description": "HTTP status code",
26+
"default": 202,
27+
"minimum": 100,
28+
"maximum": 599
29+
},
30+
"headers": {
31+
"type": "object",
32+
"description": "Additional response headers",
33+
"additionalProperties": {
34+
"type": "string"
35+
},
36+
"default": {}
37+
},
38+
"content_type": {
39+
"type": "string",
40+
"description": "Content-Type header value",
41+
"default": "application/json"
42+
},
43+
"body": {
44+
"type": "string",
45+
"description": "Response body string",
46+
"default": ""
47+
}
48+
},
49+
"additionalProperties": false
50+
}
51+
},
52+
"required": ["url"],
53+
"additionalProperties": false
54+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[plugin]
2+
name = "fire-and-forget"
3+
version = "0.1.0"
4+
type = "dispatcher"
5+
description = "Forwards request to upstream and returns an immediate static response"
6+
wasm = "fire-and-forget.wasm"
7+
8+
[capabilities]
9+
host_functions = ["host_http_call", "host_log"]

0 commit comments

Comments
 (0)