mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-13 19:02:15 +01:00
feat(webhook): add events
This commit is contained in:
@@ -2,6 +2,7 @@ package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
@@ -26,6 +27,7 @@ type client struct {
|
||||
method string
|
||||
errorUrl string
|
||||
errorMethod string
|
||||
eventsUrl string
|
||||
extraHttpHeaders map[string]string
|
||||
startTime time.Time
|
||||
|
||||
@@ -144,3 +146,66 @@ func (c client) send(ctx context.Context, body io.Reader, headers map[string]str
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendEvent sends a structured JSON event to the events URL. It is
|
||||
// fire-and-forget: failures are logged but do not propagate.
|
||||
func (c client) sendEvent(ctx context.Context, correlationIdHeader, correlationId string, event map[string]any) {
|
||||
if c.eventsUrl == "" {
|
||||
return
|
||||
}
|
||||
|
||||
b, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
c.logger.ErrorContext(ctx, fmt.Sprintf("marshal webhook event: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
tracer := gotenberg.Tracer()
|
||||
ctx, span := tracer.Start(ctx, "POST Webhook Event",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress(c.eventsUrl)),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
req, err := retryablehttp.NewRequest(http.MethodPost, c.eventsUrl, b)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
c.logger.ErrorContext(ctx, fmt.Sprintf("create webhook event request: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
|
||||
|
||||
req.Header.Set("User-Agent", "Gotenberg")
|
||||
for key, value := range c.extraHttpHeaders {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
req.Header.Set(correlationIdHeader, correlationId)
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
c.logger.ErrorContext(ctx, fmt.Sprintf("send webhook event to '%s': %s", c.eventsUrl, err))
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := resp.Body.Close()
|
||||
if err != nil {
|
||||
c.logger.ErrorContext(ctx, fmt.Sprintf("close response body from '%s': %s", c.eventsUrl, err))
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
span.RecordError(fmt.Errorf("webhook event: got status '%s'", resp.Status))
|
||||
span.SetStatus(codes.Error, resp.Status)
|
||||
c.logger.ErrorContext(ctx, fmt.Sprintf("send webhook event to '%s': got status '%s'", c.eventsUrl, resp.Status))
|
||||
return
|
||||
}
|
||||
|
||||
span.SetStatus(codes.Ok, "")
|
||||
c.logger.InfoContext(ctx, fmt.Sprintf("webhook event sent to '%s'", c.eventsUrl))
|
||||
}
|
||||
|
||||
@@ -85,7 +85,14 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
||||
if err != nil {
|
||||
params.ctx.Log().Error(fmt.Sprintf("send output file to webhook: %s", err))
|
||||
params.handleError(err)
|
||||
return
|
||||
}
|
||||
|
||||
params.client.sendEvent(params.ctx, params.correlationIdHeader, params.correlationId, map[string]any{
|
||||
"event": "webhook.success",
|
||||
"correlationId": params.correlationId,
|
||||
"timestamp": time.Now().UTC().Format(time.RFC3339Nano),
|
||||
})
|
||||
}
|
||||
|
||||
return func(c echo.Context) error {
|
||||
@@ -176,6 +183,15 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
||||
}
|
||||
}
|
||||
|
||||
// What about the events URL?
|
||||
webhookEventsUrl := c.Request().Header.Get("Gotenberg-Webhook-Events-Url")
|
||||
if webhookEventsUrl != "" {
|
||||
err = gotenberg.FilterDeadline(w.allowList, w.denyList, webhookEventsUrl, deadline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("filter webhook events URL: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve values from echo.Context before it gets recycled.
|
||||
// See https://github.com/gotenberg/gotenberg/issues/1000.
|
||||
startTime := c.Get("startTime").(time.Time)
|
||||
@@ -187,6 +203,7 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
||||
method: webhookMethod,
|
||||
errorUrl: webhookErrorUrl,
|
||||
errorMethod: webhookErrorMethod,
|
||||
eventsUrl: webhookEventsUrl,
|
||||
extraHttpHeaders: extraHttpHeaders,
|
||||
startTime: startTime,
|
||||
|
||||
@@ -233,6 +250,16 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
||||
if err != nil {
|
||||
ctx.Log().Error(fmt.Sprintf("send error response to webhook: %s", err.Error()))
|
||||
}
|
||||
|
||||
client.sendEvent(ctx, correlationIdHeader, correlationId, map[string]any{
|
||||
"event": "webhook.error",
|
||||
"correlationId": correlationId,
|
||||
"timestamp": time.Now().UTC().Format(time.RFC3339Nano),
|
||||
"error": map[string]any{
|
||||
"status": status,
|
||||
"message": message,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if w.enableSyncMode {
|
||||
|
||||
Reference in New Issue
Block a user