fix(chromium): increase the scope of loading failed errors (#962)

This commit is contained in:
Julien Neuhart
2024-09-07 15:54:01 +02:00
committed by GitHub
parent 300e73f07d
commit a3647fea8a
6 changed files with 60 additions and 31 deletions

View File

@@ -315,12 +315,14 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
} }
var ( var (
connectionRefused error loadingFailed error
connectionRefusedMu sync.RWMutex loadingFailedMu sync.RWMutex
) )
// See https://github.com/gotenberg/gotenberg/issues/913. // See:
listenForEventLoadingFailedOnConnectionRefused(taskCtx, logger, &connectionRefused, &connectionRefusedMu) // https://github.com/gotenberg/gotenberg/issues/913.
// https://github.com/gotenberg/gotenberg/issues/959.
listenForEventLoadingFailed(taskCtx, logger, &loadingFailed, &loadingFailedMu)
err = chromedp.Run(taskCtx, tasks...) err = chromedp.Run(taskCtx, tasks...)
if err != nil { if err != nil {
@@ -357,12 +359,14 @@ func (b *chromiumBrowser) do(ctx context.Context, logger *zap.Logger, url string
return fmt.Errorf("%v: %w", consoleExceptions, ErrConsoleExceptions) return fmt.Errorf("%v: %w", consoleExceptions, ErrConsoleExceptions)
} }
// See https://github.com/gotenberg/gotenberg/issues/913. // See:
connectionRefusedMu.RLock() // https://github.com/gotenberg/gotenberg/issues/913.
defer connectionRefusedMu.RUnlock() // https://github.com/gotenberg/gotenberg/issues/959.
loadingFailedMu.RLock()
defer loadingFailedMu.RUnlock()
if connectionRefused != nil { if loadingFailed != nil {
return fmt.Errorf("%v: %w", connectionRefused, ErrConnectionRefused) return fmt.Errorf("%v: %w", loadingFailed, ErrLoadingFailed)
} }
return nil return nil

View File

@@ -480,7 +480,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
expectedError: ErrConsoleExceptions, expectedError: ErrConsoleExceptions,
}, },
{ {
scenario: "ErrConnectionRefused", scenario: "ErrLoadingFailed",
browser: newChromiumBrowser( browser: newChromiumBrowser(
browserArguments{ browserArguments{
binPath: os.Getenv("CHROMIUM_BIN_PATH"), binPath: os.Getenv("CHROMIUM_BIN_PATH"),
@@ -503,7 +503,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
noDeadline: false, noDeadline: false,
start: true, start: true,
expectError: true, expectError: true,
expectedError: ErrConnectionRefused, expectedError: ErrLoadingFailed,
}, },
{ {
scenario: "clear cache", scenario: "clear cache",
@@ -1553,7 +1553,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
expectedError: ErrConsoleExceptions, expectedError: ErrConsoleExceptions,
}, },
{ {
scenario: "ErrConnectionRefused", scenario: "ErrLoadingFailed",
browser: newChromiumBrowser( browser: newChromiumBrowser(
browserArguments{ browserArguments{
binPath: os.Getenv("CHROMIUM_BIN_PATH"), binPath: os.Getenv("CHROMIUM_BIN_PATH"),
@@ -1577,7 +1577,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
noDeadline: false, noDeadline: false,
start: true, start: true,
expectError: true, expectError: true,
expectedError: ErrConnectionRefused, expectedError: ErrLoadingFailed,
}, },
{ {
scenario: "clear cache", scenario: "clear cache",

View File

@@ -42,8 +42,8 @@ var (
// is set to true. // is set to true.
ErrConsoleExceptions = errors.New("console exceptions") ErrConsoleExceptions = errors.New("console exceptions")
// ErrConnectionRefused happens when a URL cannot be reached. // ErrLoadingFailed happens when a URL failed to load.
ErrConnectionRefused = errors.New("connection refused") ErrLoadingFailed = errors.New("loading failed")
// PDF specific. // PDF specific.

View File

@@ -21,7 +21,8 @@ import (
) )
// listenForEventRequestPaused listens for requests to check if they are // listenForEventRequestPaused listens for requests to check if they are
// allowed or not. // allowed or not.network.SetBlockedURLS()
// TODO: https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-setBlockedURLs (experimental for now).
func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowList *regexp2.Regexp, denyList *regexp2.Regexp) { func listenForEventRequestPaused(ctx context.Context, logger *zap.Logger, allowList *regexp2.Regexp, denyList *regexp2.Regexp) {
chromedp.ListenTarget(ctx, func(ev interface{}) { chromedp.ListenTarget(ctx, func(ev interface{}) {
switch e := ev.(type) { switch e := ev.(type) {
@@ -95,24 +96,48 @@ func listenForEventResponseReceived(ctx context.Context, logger *zap.Logger, url
}) })
} }
// listenForEventLoadingFailedOnConnectionRefused listens for an event // listenForEventLoadingFailed listens for an event indicating that the main
// indicating that the main page failed to load. // page failed to load.
// See https://github.com/gotenberg/gotenberg/issues/913. // See:
func listenForEventLoadingFailedOnConnectionRefused(ctx context.Context, logger *zap.Logger, connectionRefused *error, connectionRefusedMu *sync.RWMutex) { // https://github.com/gotenberg/gotenberg/issues/913.
// https://github.com/gotenberg/gotenberg/issues/959.
func listenForEventLoadingFailed(ctx context.Context, logger *zap.Logger, loadingFailed *error, loadingFailedMu *sync.RWMutex) {
chromedp.ListenTarget(ctx, func(ev interface{}) { chromedp.ListenTarget(ctx, func(ev interface{}) {
switch ev := ev.(type) { switch ev := ev.(type) {
case *network.EventLoadingFailed: case *network.EventLoadingFailed:
logger.Debug(fmt.Sprintf("event EventLoadingFailed fired: %+v", ev.ErrorText)) logger.Debug(fmt.Sprintf("event EventLoadingFailed fired: %+v", ev.ErrorText))
if ev.ErrorText != "net::ERR_CONNECTION_REFUSED" || ev.Type != network.ResourceTypeDocument { if ev.Type != network.ResourceTypeDocument {
logger.Debug("skip EventLoadingFailed: is not net::ERR_CONNECTION_REFUSED and/or resource type Document") logger.Debug("skip EventLoadingFailed: is not resource type Document")
return return
} }
connectionRefusedMu.Lock() // Supposition: except iframe, an event loading failed with a
defer connectionRefusedMu.Unlock() // resource type Document is about the main page.
*connectionRefused = fmt.Errorf("%s", ev.ErrorText) // We are looking for common errors.
// TODO: sufficient?
errors := []string{
"net::ERR_CONNECTION_CLOSED",
"net::ERR_CONNECTION_RESET",
"net::ERR_CONNECTION_REFUSED",
"net::ERR_CONNECTION_ABORTED",
"net::ERR_CONNECTION_FAILED",
"net::ERR_NAME_NOT_RESOLVED",
"net::ERR_INTERNET_DISCONNECTED",
"net::ERR_ADDRESS_UNREACHABLE",
"net::ERR_BLOCKED_BY_CLIENT",
"net::ERR_BLOCKED_BY_RESPONSE",
}
if !slices.Contains(errors, ev.ErrorText) {
logger.Debug(fmt.Sprintf("skip EventLoadingFailed: '%s' is not part of %+v", ev.ErrorText, errors))
return
}
loadingFailedMu.Lock()
defer loadingFailedMu.Unlock()
*loadingFailed = fmt.Errorf("%s", ev.ErrorText)
} }
}) })
} }

View File

@@ -688,12 +688,12 @@ func handleChromiumError(err error, options Options) error {
) )
} }
if errors.Is(err, ErrConnectionRefused) { if errors.Is(err, ErrLoadingFailed) {
return api.WrapError( return api.WrapError(
err, err,
api.NewSentinelHttpError( api.NewSentinelHttpError(
http.StatusBadRequest, http.StatusBadRequest,
"Chromium returned net::ERR_CONNECTION_REFUSED", fmt.Sprintf("Chromium returned %v", err),
), ),
) )
} }

View File

@@ -1436,10 +1436,10 @@ func TestConvertUrl(t *testing.T) {
expectOutputPathsCount: 0, expectOutputPathsCount: 0,
}, },
{ {
scenario: "ErrConnectionRefused", scenario: "ErrLoadingFailed",
ctx: &api.ContextMock{Context: new(api.Context)}, ctx: &api.ContextMock{Context: new(api.Context)},
api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error { api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
return ErrConnectionRefused return ErrLoadingFailed
}}, }},
options: DefaultPdfOptions(), options: DefaultPdfOptions(),
expectError: true, expectError: true,
@@ -1646,10 +1646,10 @@ func TestScreenshotUrl(t *testing.T) {
expectOutputPathsCount: 0, expectOutputPathsCount: 0,
}, },
{ {
scenario: "ErrConnectionRefused", scenario: "ErrLoadingFailed",
ctx: &api.ContextMock{Context: new(api.Context)}, ctx: &api.ContextMock{Context: new(api.Context)},
api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error { api: &ApiMock{ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
return ErrConnectionRefused return ErrLoadingFailed
}}, }},
options: DefaultScreenshotOptions(), options: DefaultScreenshotOptions(),
expectError: true, expectError: true,