All checks were successful
check / check (push) Successful in 5s
Each target TYPE is now an implementation of a Target interface, dispatched from a registry in processDelivery instead of a type switch on TargetType. Every target owns its full delivery, including durable retries. - Target.Deliver receives the context, the per-webhook DB, the Delivery, the attempt Task, and a Scheduler for durable re-enqueue (the existing timer + retry queue). The target makes one attempt, records the DeliveryResult, updates DeliveryStatus, and — for retry targets — decides whether to retry, computes its own backoff, gates with its own circuit breaker, and reschedules via the Scheduler. - httpTarget and slackTarget share a retry core (retry, backoff, circuit breaker). database and log targets are fire-and-forget. - Slack retry/breaker is gated on MaxRetries: 0 stays fire-and-forget (existing Slack targets unchanged), >0 gets retry + backoff + breaker on the shared core. - The engine keeps only the worker pool, queue/channels, restart recovery/sweep, the recordResult/updateDeliveryStatus helpers, and ScheduleRetry. Recovery/sweep hand each orphaned retrying delivery back to its target to recompute the backoff. - The log target logs the entire inbound webhook: full body and headers, method, content type, and the webhook and entrypoint ids (supersedes the smaller log-summary work). - Task gains EntrypointID, populated in the webhook handler, the recovery-task builder, and buildEventFromTask. Behaviour is preserved: existing delivery tests pass with their export_test wrappers re-pointed at the new targets; new pure Deliver tests cover the log full-content output and the gated Slack retry path.
102 lines
2.9 KiB
Go
102 lines
2.9 KiB
Go
package delivery
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
)
|
|
|
|
// Scheduler re-enqueues a task for a future delivery attempt.
|
|
// The engine provides one to each target so a target can own
|
|
// its retries durably: it records the attempt, marks the
|
|
// delivery retrying, and asks the Scheduler to deliver the
|
|
// next attempt after delay — exactly what the engine does for
|
|
// its own restart recovery.
|
|
type Scheduler interface {
|
|
ScheduleRetry(task Task, delay time.Duration)
|
|
}
|
|
|
|
// Target delivers an event to one target type. Each type is
|
|
// an implementation. A Target owns its whole delivery: it
|
|
// makes the attempt, records the DeliveryResult and updates
|
|
// the DeliveryStatus, and — for targets that retry — decides
|
|
// whether to retry, computes its own backoff, gates with its
|
|
// own circuit breaker, and reschedules via the injected
|
|
// Scheduler. Fire-and-forget targets simply record a single
|
|
// attempt.
|
|
type Target interface {
|
|
Deliver(
|
|
ctx context.Context,
|
|
webhookDB *gorm.DB,
|
|
d *database.Delivery,
|
|
task *Task,
|
|
sched Scheduler,
|
|
)
|
|
}
|
|
|
|
// rescheduler is implemented by targets that own durable
|
|
// retries. The engine's restart recovery and periodic sweep
|
|
// use it to let the target recompute the schedule for an
|
|
// orphaned retrying delivery, keeping the retry schedule
|
|
// target-owned. Fire-and-forget targets do not implement it
|
|
// and their (never-occurring) retrying deliveries are
|
|
// skipped.
|
|
type rescheduler interface {
|
|
// remainingBackoff returns how long to wait before the
|
|
// next attempt of a recovered retrying delivery.
|
|
remainingBackoff(
|
|
webhookDB *gorm.DB,
|
|
deliveryID string,
|
|
attemptNum int,
|
|
) time.Duration
|
|
|
|
// backoffElapsed reports whether the backoff window for
|
|
// the last attempt has already passed, so the periodic
|
|
// sweep can re-enqueue the delivery now.
|
|
backoffElapsed(
|
|
webhookDB *gorm.DB,
|
|
deliveryID string,
|
|
attemptNum int,
|
|
) bool
|
|
}
|
|
|
|
// attemptResult is the outcome of a single delivery attempt,
|
|
// as reported by a target's per-attempt function to the
|
|
// shared retry core.
|
|
type attemptResult struct {
|
|
statusCode int
|
|
respBody string
|
|
duration int64
|
|
success bool
|
|
errMsg string
|
|
}
|
|
|
|
// initTargets builds the target registry, wiring each target
|
|
// to the engine's persistence helpers and giving the HTTP and
|
|
// Slack targets the shared SSRF-safe client. It is called by
|
|
// both New and the test constructors so the registry is
|
|
// always populated.
|
|
func (e *Engine) initTargets(client *http.Client) {
|
|
httpT := &httpTarget{
|
|
httpCore: &httpCore{eng: e},
|
|
client: client,
|
|
}
|
|
|
|
slackT := &slackTarget{
|
|
httpCore: &httpCore{eng: e},
|
|
client: client,
|
|
}
|
|
|
|
e.httpTarget = httpT
|
|
|
|
e.targets = map[database.TargetType]Target{
|
|
database.TargetTypeHTTP: httpT,
|
|
database.TargetTypeSlack: slackT,
|
|
database.TargetTypeDatabase: &databaseTarget{eng: e},
|
|
database.TargetTypeLog: &logTarget{eng: e},
|
|
}
|
|
}
|