fix(pdfengines): read metadata better webhook error - fixes #1148

This commit is contained in:
Julien Neuhart
2025-05-15 09:22:52 +02:00
parent 66317197b6
commit 9aaab97d6a
5 changed files with 48 additions and 24 deletions

View File

@@ -500,6 +500,11 @@ func readMetadataRoute(engine gotenberg.PdfEngine) api.Route {
err = c.JSON(http.StatusOK, res)
if err != nil {
if strings.Contains(err.Error(), "request method or response status code does not allow body") {
// High probability that the user is using the webhook
// feature. It does not make sense for this route.
return api.ErrNoOutputFile
}
return fmt.Errorf("return JSON response: %w", err)
}

View File

@@ -184,11 +184,21 @@ func webhookMiddleware(w *Webhook) api.Middleware {
// Call the next middleware in the chain.
err := next(c)
if err != nil {
if errors.Is(err, api.ErrNoOutputFile) {
errNoOutputFile := fmt.Errorf("%w - the webhook middleware cannot handle the result of this route", err)
handleAsyncError(api.WrapError(
errNoOutputFile,
api.NewSentinelHttpError(
http.StatusBadRequest,
"The webhook middleware can only work with multipart/form-data routes that results in output files",
),
))
return
}
// The process failed for whatever reason. Let's send the
// details to the webhook.
ctx.Log().Error(err.Error())
handleAsyncError(err)
return
}
@@ -197,7 +207,6 @@ func webhookMiddleware(w *Webhook) api.Middleware {
if err != nil {
ctx.Log().Error(fmt.Sprintf("build output file: %s", err))
handleAsyncError(err)
return
}
@@ -205,7 +214,6 @@ func webhookMiddleware(w *Webhook) api.Middleware {
if err != nil {
ctx.Log().Error(fmt.Sprintf("open output file: %s", err))
handleAsyncError(err)
return
}
@@ -221,7 +229,6 @@ func webhookMiddleware(w *Webhook) api.Middleware {
if err != nil {
ctx.Log().Error(fmt.Sprintf("read header of output file: %s", err))
handleAsyncError(err)
return
}
@@ -229,7 +236,6 @@ func webhookMiddleware(w *Webhook) api.Middleware {
if err != nil {
ctx.Log().Error(fmt.Sprintf("get stat from output file: %s", err))
handleAsyncError(err)
return
}
@@ -237,7 +243,6 @@ func webhookMiddleware(w *Webhook) api.Middleware {
if err != nil {
ctx.Log().Error(fmt.Sprintf("reset output file reader: %s", err))
handleAsyncError(err)
return
}

View File

@@ -233,6 +233,24 @@ Feature: /forms/pdfengines/{write|read}
}
"""
Scenario: POST /forms/pdfengines/metadata/read (Webhook)
Given I have a default Gotenberg container
Given I have a webhook server
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s):
| files | testdata/page_1.pdf | file |
| Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header |
| Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header |
Then the response status code should be 204
When I wait for the asynchronous request to the webhook
Then the webhook request header "Content-Type" should be "application/json"
Then the webhook request body should match JSON:
"""
{
"status": 400,
"message": "the webhook middleware can only work with multipart/form-data routes that results in output files"
}
"""
Scenario: POST /forms/pdfengines/metadata/write (Basic Auth)
Given I have a Gotenberg container with the following environment variable(s):
| API_ENABLE_BASIC_AUTH | true |

View File

@@ -407,11 +407,7 @@ func (s *scenario) theBodyShouldMatchString(kind string, expectedDoc *godog.DocS
} else if s.server.req == nil {
return errors.New("no webhook request found")
} else {
body, err := io.ReadAll(s.server.req.Body)
if err != nil {
return fmt.Errorf("read request body: %w", err)
}
actual = string(body)
actual = string(s.server.bodyCopy)
}
expected := strings.ReplaceAll(expectedDoc.Content, "{version}", GotenbergVersion)
@@ -431,11 +427,7 @@ func (s *scenario) theBodyShouldContainString(kind string, expectedDoc *godog.Do
} else if s.server.req == nil {
return errors.New("no webhook request found")
} else {
body, err := io.ReadAll(s.server.req.Body)
if err != nil {
return fmt.Errorf("read request body: %w", err)
}
actual = string(body)
actual = string(s.server.bodyCopy)
}
expected := strings.ReplaceAll(expectedDoc.Content, "{version}", GotenbergVersion)
@@ -455,11 +447,7 @@ func (s *scenario) theBodyShouldMatchJSON(kind string, expectedDoc *godog.DocStr
} else if s.server.req == nil {
return errors.New("no webhook request found")
} else {
b, err := io.ReadAll(s.server.req.Body)
if err != nil {
return fmt.Errorf("read request body: %w", err)
}
body = b
body = s.server.bodyCopy
}
var expected, actual interface{}

View File

@@ -19,9 +19,10 @@ import (
)
type server struct {
srv *echo.Echo
req *http.Request
errChan chan error
srv *echo.Echo
req *http.Request
bodyCopy []byte
errChan chan error
}
func newServer(ctx context.Context, workdir string) (*server, error) {
@@ -51,6 +52,8 @@ func newServer(ctx context.Context, workdir string) (*server, error) {
return webhookErr(fmt.Errorf("read request body: %w", err))
}
s.bodyCopy = body
cd := s.req.Header.Get("Content-Disposition")
if cd == "" {
return webhookErr(fmt.Errorf("no Content-Disposition header"))
@@ -125,6 +128,11 @@ func newServer(ctx context.Context, workdir string) (*server, error) {
}
webhookErrorHandler := func(c echo.Context) error {
s.req = c.Request()
body, err := io.ReadAll(s.req.Body)
if err != nil {
return webhookErr(fmt.Errorf("read request body: %w", err))
}
s.bodyCopy = body
return webhookErr(c.String(http.StatusOK, http.StatusText(http.StatusOK)))
}