From ccb9c7696891b2f7371ea55cb241c9338cbb065d Mon Sep 17 00:00:00 2001 From: Stanislav Pankevich Date: Tue, 9 Dec 2025 22:31:22 +0100 Subject: [PATCH] feat(server, html2pdf): implement better error reporting when Chrome is not found **WHAT:** This change improves the error message when the StrictDoc web server cannot find a Chrome installation on the system it is running on. **WHY:** Before this change, the error message was a simple Internal Server Error without an explicit indication that the Chrome could not be found. A user would have to dig through the server logs to understand the issue. **HOW:** This integrates the upstream html2pdf4doc work where the error reporting was improved in the main.py driver program. In particular, the driver now exits with a specific exit code `COULD_NOT_FIND_CHROME = 5` that we can rely on in StrictDoc to identify the missing Chrome issue. https://github.com/strictdoc-project/html2pdf4doc_python/issues/58 https://github.com/strictdoc-project/html2pdf4doc_python/pull/70 --- pyproject.toml | 2 +- strictdoc/export/html2pdf/pdf_print_driver.py | 48 +++++++++++++++++-- strictdoc/server/routers/main_router.py | 16 +++++-- 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1f595ae79..25c4fbdab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ dependencies = [ "WebSockets", # HTML2PDF dependencies - "html2pdf4doc >= 0.0.23", + "html2pdf4doc >= 0.0.24", # Robot Framework dependencies "robotframework >= 4.0.0", diff --git a/strictdoc/export/html2pdf/pdf_print_driver.py b/strictdoc/export/html2pdf/pdf_print_driver.py index ccdfffcad..d5a69715a 100644 --- a/strictdoc/export/html2pdf/pdf_print_driver.py +++ b/strictdoc/export/html2pdf/pdf_print_driver.py @@ -3,13 +3,53 @@ """ import os.path -from subprocess import CompletedProcess, TimeoutExpired, run +from subprocess import CalledProcessError, CompletedProcess, TimeoutExpired, run from typing import List, Tuple +from html2pdf4doc.main import HPDExitCode + from strictdoc.core.project_config import ProjectConfig from strictdoc.helpers.timing import measure_performance +class PDFPrintDriverException(Exception): + def __init__(self, exception: Exception): + super().__init__() + self.exception: Exception = exception + + def get_server_user_message(self) -> str: + """ + Provide a user-friendly message that describes the underlying exception/error. + """ + + if self.is_could_not_detect_chrome(): + return "HTML2PDF could not detect an existing Chrome installation." + + if self.is_timeout_error(): + return "HTML2PDF timeout error." + + if self.is_js_success_timeout(): + return "HTML2PDF.js success timeout error." + + return "HTML2PDF internal error." + + def is_timeout_error(self) -> bool: + return isinstance(self.exception, TimeoutExpired) + + def is_could_not_detect_chrome(self) -> bool: + return ( + isinstance(self.exception, CalledProcessError) + and self.exception.returncode == HPDExitCode.COULD_NOT_FIND_CHROME + ) + + def is_js_success_timeout(self) -> bool: + return ( + isinstance(self.exception, CalledProcessError) + and self.exception.returncode + == HPDExitCode.DID_NOT_RECEIVE_SUCCESS_STATUS_FROM_HTML2PDF4DOC_JS + ) + + class PDFPrintDriver: @staticmethod def get_pdf_from_html( @@ -52,7 +92,7 @@ def get_pdf_from_html( _: CompletedProcess[bytes] = run( cmd, capture_output=False, - check=False, + check=True, ) - except TimeoutExpired: - raise TimeoutError from None + except Exception as e_: + raise PDFPrintDriverException(e_) from e_ diff --git a/strictdoc/server/routers/main_router.py b/strictdoc/server/routers/main_router.py index 4f54bd296..53936e3dd 100644 --- a/strictdoc/server/routers/main_router.py +++ b/strictdoc/server/routers/main_router.py @@ -113,7 +113,10 @@ from strictdoc.export.html.html_templates import HTMLTemplates, JinjaEnvironment from strictdoc.export.html.renderers.link_renderer import LinkRenderer from strictdoc.export.html.renderers.markup_renderer import MarkupRenderer -from strictdoc.export.html2pdf.pdf_print_driver import PDFPrintDriver +from strictdoc.export.html2pdf.pdf_print_driver import ( + PDFPrintDriver, + PDFPrintDriverException, +) from strictdoc.export.json.json_generator import JSONGenerator from strictdoc.helpers.cast import assert_cast from strictdoc.helpers.file_modification_time import ( @@ -134,7 +137,7 @@ HTTP_STATUS_BAD_REQUEST = 400 HTTP_STATUS_PRECONDITION_FAILED = 412 -HTTP_STATUS_GATEWAY_TIMEOUT = 504 +HTTP_STATUS_INTERNAL_SERVER_ERROR = 500 AUTOCOMPLETE_LIMIT = 50 @@ -2148,6 +2151,9 @@ def get_export_html2pdf(document_mid: str) -> Response: # noqa: ARG001 path_to_output_pdf = os.path.join( project_config.export_output_html_root, "html", "_temp.pdf" ) + + # FIXME: Add this print driver to a service bus object to make it + # unit-testable. pdf_print_driver = PDFPrintDriver() with open(path_to_output_html, mode="w", encoding="utf8") as temp_file_: temp_file_.write(document_content) @@ -2158,10 +2164,10 @@ def get_export_html2pdf(document_mid: str) -> Response: # noqa: ARG001 project_config, [(path_to_output_html, path_to_output_pdf)], ) - except TimeoutError: # pragma: no cover + except PDFPrintDriverException as e_: # pragma: no cover return Response( - content="HTML2PDF timeout error.", - status_code=HTTP_STATUS_GATEWAY_TIMEOUT, + content=e_.get_server_user_message(), + status_code=HTTP_STATUS_INTERNAL_SERVER_ERROR, ) return FileResponse(