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(