diff --git a/src/go/cmd/zabbix_web_service/pdf_report_creator.go b/src/go/cmd/zabbix_web_service/pdf_report_creator.go index a24daee3b8c..cb20de1d367 100644 --- a/src/go/cmd/zabbix_web_service/pdf_report_creator.go +++ b/src/go/cmd/zabbix_web_service/pdf_report_creator.go @@ -44,6 +44,11 @@ type requestBody struct { Parameters map[string]string `json:"parameters"` } +type chromedpResp struct { + data []byte + err error +} + func newRequestBody() *requestBody { return &requestBody{"", make(map[string]string), make(map[string]string)} } @@ -184,23 +189,66 @@ func (h *handler) report(w http.ResponseWriter, r *http.Request) { cookieParams = append(cookieParams, &cookieParam) } - errEvtC := make(chan string) + respChan := make(chan chromedpResp) + defer close(respChan) + + go runCDP(ctx, cancel, cookieParams, width, height, u.String(), respChan) - go networkErrEvtListener(errEvtC, cancel, u.String(), w) + // should never deadlock as chromedp.Run has it's own timeout and we should always get a response + resp := <-respChan - chromedp.ListenTarget(ctx, handleNetworkErrEvt(errEvtC)) + if resp.err != nil { + logAndWriteError( + w, + errs.WrapConst(resp.err, zbxerr.ErrorCannotFetchData).Error(), + http.StatusInternalServerError, + ) - var buf []byte + return + } - err = chromedp.Run(ctx, chromedp.Tasks{ + log.Infof("writing response for report request from %s", r.RemoteAddr) + + w.Header().Set("Content-type", "application/pdf") + w.Write(resp.data) +} + +func runCDP( + ctx context.Context, + cancel context.CancelFunc, + cookieParams []*network.CookieParam, + width, height int64, + url string, + resp chan<- chromedpResp, +) { + + var ( + out []byte + listenerErr error + ) + + chromedp.ListenTarget( + ctx, + func(ev any) { + failEvent, ok := ev.(*network.EventLoadingFailed) + if !ok { + return + } + + listenerErr = parseNetworkErr(failEvent.ErrorText) + cancel() + }, + ) + + err := chromedp.Run(ctx, chromedp.Tasks{ network.SetCookies(cookieParams), emulation.SetDeviceMetricsOverride(width, height, 1, false), - prepareDashboard(u.String()), + prepareDashboard(url), chromedp.ActionFunc(func(ctx context.Context) error { timeoutContext, cancel := context.WithTimeout(ctx, time.Duration(options.Timeout)*time.Second) defer cancel() var err error - buf, _, err = page.PrintToPDF(). + out, _, err = page.PrintToPDF(). WithPrintBackground(true). WithPreferCSSPageSize(true). WithPaperWidth(pixels2inches(width)). @@ -211,27 +259,20 @@ func (h *handler) report(w http.ResponseWriter, r *http.Request) { }), }) - if err != nil { - if errors.Is(err, context.Canceled) { - // error is written by networkEvtLoadingFailedListener, errEvtC channel is closed - return - } - - close(errEvtC) - - logAndWriteError(w, zbxerr.ErrorCannotFetchData.Wrap(err).Error(), http.StatusInternalServerError) - + if listenerErr != nil { + // error is logged since in case of listenerErr chromedp error might be nil or some other error, + // and it is good for debugging. + log.Tracef("chromedp.Run exited, with err: %v", err) + resp <- chromedpResp{err: listenerErr} return } - close(errEvtC) - - log.Infof("writing response for report request from %s", r.RemoteAddr) - - w.Header().Set("Content-type", "application/pdf") - w.Write(buf) + if err != nil { + resp <- chromedpResp{err: err} + return + } - return + resp <- chromedpResp{data: out} } func pixels2inches(value int64) float64 { @@ -290,42 +331,19 @@ func parseUrl(u string) (*url.URL, error) { return parsed, nil } -// networkErrEvtListener listens errC channel for error messages, and processes them with logAndWriteError. -func networkErrEvtListener(errC <-chan string, ctxCancel context.CancelFunc, addr string, w http.ResponseWriter) { - for errStr := range errC { - switch errStr { - case netErrCertAuthorityInvalid: - errStr = fmt.Sprintf( - "Invalid certificate authority detected while loading dashboard: '%s'. Fix TLS "+ - "configuration or configure Zabbix web service to ignore TLS certificate "+ - "errors when accessing frontend URL.", - addr, - ) - case "": - errStr = "network.EventLoadingFailed event with empty ErrorText was received while loading " + - "dashboard." - default: - errStr = fmt.Sprintf( - "network.EventLoadingFailed event with ErrorText = '%s' was received while loading "+ - "dashboard.", - errStr, - ) - } - - logAndWriteError(w, errStr, http.StatusInternalServerError) - ctxCancel() - } -} - -// handleNetworkErrEvt returns a function that handles network.EventLoadingFailed events. -func handleNetworkErrEvt(errEvtC chan string) func(ev any) { - return func(ev any) { - failEvent, ok := ev.(*network.EventLoadingFailed) - if !ok { - return - } - - errEvtC <- failEvent.ErrorText - close(errEvtC) +func parseNetworkErr(errStr string) error { + switch errStr { + case netErrCertAuthorityInvalid: + return errs.Errorf( + "Invalid certificate authority detected while loading dashboard. Fix TLS " + + "configuration or configure Zabbix web service to ignore TLS certificate " + + "errors when accessing frontend URL.", + ) + case "": + return errs.New("network.EventLoadingFailed event with empty ErrorText was received while loading dashboard.") + default: + return errs.Errorf( + "network.EventLoadingFailed event with ErrorText = '%s' was received while loading dashboard.", errStr, + ) } }