diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f352c3b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.8.1] - 2022-06-06 + +### Added + +- Update whtml drivers 0.12.1 -> 0.12.4 https://github.com/wkhtmltopdf/wkhtmltopdf/releases/tag/0.12.4 (based on https://github.com/webgio/Rotativa/pull/115) +- Fix try to kill process when cancellation requested + +## [1.8.0] - 2022-06-03 + +### Added + +- Add timeout support https://github.com/webgio/Rotativa/issues/203 + +### Fixed + +- fix when WkhtmlDriver exits with error code https://github.com/webgio/Rotativa/issues/189 +- fix using HttpContext.Current instead passed ControllerContext https://github.com/webgio/Rotativa/pull/178 + diff --git a/Rotativa.Demo/Controllers/HomeController.cs b/Rotativa.Demo/Controllers/HomeController.cs index 39f121f..a6fd892 100644 --- a/Rotativa.Demo/Controllers/HomeController.cs +++ b/Rotativa.Demo/Controllers/HomeController.cs @@ -160,6 +160,21 @@ public ActionResult ErrorTest() return new ActionAsPdf("SomethingBad") { FileName = "Test.pdf" }; } + public ActionResult HttpStatus500Test() + { + return new UrlAsPdf("https://httpstat.us/500") { FileName = "Test.pdf" }; + + } + public ActionResult HttpStatus404Test() + { + return new UrlAsPdf("https://httpstat.us/404") { FileName = "Test.pdf" }; + } + public ActionResult HttpStatus200Test() + { + return new UrlAsPdf("https://httpstat.us/200") { FileName = "Test.pdf" }; + + } + public ActionResult SomethingBad() { return Redirect("http://thisdoesntexists"); diff --git a/Rotativa.Demo/Views/Home/Index.cshtml b/Rotativa.Demo/Views/Home/Index.cshtml index 8000b1f..ecd4d6d 100644 --- a/Rotativa.Demo/Views/Home/Index.cshtml +++ b/Rotativa.Demo/Views/Home/Index.cshtml @@ -4,30 +4,33 @@

@ViewBag.Message

-

+

diff --git a/Rotativa.Tests/RotativaTests.cs b/Rotativa.Tests/RotativaTests.cs index 3442a36..d54194b 100644 --- a/Rotativa.Tests/RotativaTests.cs +++ b/Rotativa.Tests/RotativaTests.cs @@ -360,5 +360,40 @@ public void Can_print_image_from_page_with_external_css_file() image.RawFormat.Should().Be.EqualTo(ImageFormat.Jpeg); } } + + [Test] + public void HttpError_statuses_does_not_generate_pdf_file() + { + var testLink = selenium.FindElement(By.LinkText("HttpStatus 200 Test")); + var pdfHref = testLink.GetAttribute("href"); + + var content = "200 OK"; + using (var wc = new WebClient()) + { + var pdfResult = wc.DownloadData(new Uri(pdfHref)); + var pdfTester = new PdfTester(); + pdfTester.LoadPdf(pdfResult); + pdfTester.PdfIsValid.Should().Be.True(); + pdfTester.PdfContains(content).Should().Be.True(); + } + + testLink = selenium.FindElement(By.LinkText("HttpStatus 404 Test")); + pdfHref = testLink.GetAttribute("href"); + + + using (var wc = new WebClient()) + { + Assert.Throws(typeof(WebException), () => wc.DownloadData(new Uri(pdfHref))); + } + + testLink = selenium.FindElement(By.LinkText("HttpStatus 500 Test")); + pdfHref = testLink.GetAttribute("href"); + + + using (var wc = new WebClient()) + { + Assert.Throws(typeof(WebException), () => wc.DownloadData(new Uri(pdfHref))); + } + } } } diff --git a/Rotativa.UnitTests/BinaryTests.cs b/Rotativa.UnitTests/BinaryTests.cs index c254a98..fe12007 100644 --- a/Rotativa.UnitTests/BinaryTests.cs +++ b/Rotativa.UnitTests/BinaryTests.cs @@ -23,20 +23,19 @@ namespace Rotativa.UnitTests [TestFixture] public class BinaryTests { + private const string TestUrl = "https://github.com/webgio/Rotativa"; + [Test] public void Can_build_the_pdf_binary() { - var localPath = Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory); - var solutionDir = localPath.Parent.Parent.Parent.FullName; - var wkhtmltopdfPath = Path.Combine(solutionDir, "Rotativa", "Rotativa"); - var actionResult = new UrlAsPdf("https://github.com/webgio/Rotativa") - { - WkhtmltopdfPath = wkhtmltopdfPath - }; - var builder = new TestControllerBuilder(); - var controller = new HomeController(); - builder.InitializeController(controller); - var pdfBinary = actionResult.BuildPdf(controller.ControllerContext); + //Arrange + var actionResult = CreatePdfActionResult(); + var controller = CreateTestController(); + + //Act + var pdfBinary = actionResult.BuildFile(controller.ControllerContext); + + //Assert var pdfTester = new PdfTester(); pdfTester.LoadPdf(pdfBinary); pdfTester.PdfIsValid.Should().Be.True(); @@ -44,23 +43,82 @@ public void Can_build_the_pdf_binary() } [Test] - public void Can_build_the_image_binary() + public void Failed_to_build_the_pdf_binary_when_timeout_exceed() { - var localPath = Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory); - var solutionDir = localPath.Parent.Parent.Parent.FullName; - var wkhtmltoimagePath = Path.Combine(solutionDir, "Rotativa", "Rotativa"); - var actionResult = new UrlAsImage("https://github.com/webgio/Rotativa") + //Arrange + var actionResult = CreatePdfActionResult(convertTimeout: 1); + var controller = CreateTestController(); + void TestMethod() { - WkhtmlPath = wkhtmltoimagePath - }; - var builder = new TestControllerBuilder(); - var controller = new HomeController(); - builder.InitializeController(controller); + actionResult.BuildFile(controller.ControllerContext); + } + //Act + + //Assert + Assert.Throws(TestMethod); + + } + + [Test] + public void Can_build_the_image_binary() + { + //Arrange + var actionResult = CreateImageActionResult(); + var controller = CreateTestController(); + + //Act var imageBinary = actionResult.BuildFile(controller.ControllerContext); + //Assert var image = Image.FromStream(new MemoryStream(imageBinary)); image.Should().Not.Be.Null(); image.RawFormat.Should().Be.EqualTo(ImageFormat.Jpeg); } + + [Test] + public void Failed_to_build_the_image_binary_when_timeout_exceed() + { + //Arrange + var actionResult = CreateImageActionResult(convertTimeout: 1); + var controller = CreateTestController(); + void TestMethod() + { + actionResult.BuildFile(controller.ControllerContext); + } + //Act + + //Assert + Assert.Throws(TestMethod); + } + + private static AsImageResultBase CreateImageActionResult(string url = TestUrl, int? convertTimeout = null) + => new UrlAsImage(url) + { + WkhtmlPath = GetWkhtmlPath(), + ConvertTimeout = convertTimeout, + }; + + private static AsPdfResultBase CreatePdfActionResult(string url = TestUrl, int? convertTimeout = null) + => new UrlAsPdf(url) + { + WkhtmlPath = GetWkhtmlPath(), + ConvertTimeout = convertTimeout, + }; + + private static string GetWkhtmlPath() + { + var localPath = Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory); + var solutionDir = localPath.Parent.Parent.Parent.FullName; + var wkhtmlPath = Path.Combine(solutionDir, "Rotativa", "Rotativa"); + return wkhtmlPath; + } + + private static Controller CreateTestController() + { + var builder = new TestControllerBuilder(); + var controller = new HomeController(); + builder.InitializeController(controller); + return controller; + } } } diff --git a/Rotativa/AsImageResultBase.cs b/Rotativa/AsImageResultBase.cs index 9ad0830..71bdeca 100644 --- a/Rotativa/AsImageResultBase.cs +++ b/Rotativa/AsImageResultBase.cs @@ -56,7 +56,7 @@ public abstract class AsImageResultBase : AsResultBase protected override byte[] WkhtmlConvert(string switches) { - return WkhtmltoimageDriver.Convert(this.WkhtmlPath, switches); + return WkhtmltoimageDriver.Convert(this.WkhtmlPath, switches, timeout: ConvertTimeout); } protected override string GetContentType() diff --git a/Rotativa/AsPdfResultBase.cs b/Rotativa/AsPdfResultBase.cs index f638dbf..f30eb9c 100644 --- a/Rotativa/AsPdfResultBase.cs +++ b/Rotativa/AsPdfResultBase.cs @@ -47,7 +47,7 @@ protected AsPdfResultBase() protected override byte[] WkhtmlConvert(string switches) { - return WkhtmltopdfDriver.Convert(this.WkhtmlPath, switches); + return WkhtmltopdfDriver.Convert(this.WkhtmlPath, switches, timeout: ConvertTimeout); } protected override string GetContentType() diff --git a/Rotativa/AsResultBase.cs b/Rotativa/AsResultBase.cs index 0bf12eb..5c1c166 100644 --- a/Rotativa/AsResultBase.cs +++ b/Rotativa/AsResultBase.cs @@ -104,6 +104,11 @@ public string CookieName public ContentDisposition ContentDisposition { get; set; } + /// + /// Timeout for converting to PDF + /// + public int? ConvertTimeout { get; set; } + protected abstract string GetUrl(ControllerContext context); /// @@ -185,7 +190,7 @@ public byte[] BuildFile(ControllerContext context) throw new ArgumentNullException("context"); if (this.WkhtmlPath == string.Empty) - this.WkhtmlPath = HttpContext.Current.Server.MapPath("~/Rotativa"); + this.WkhtmlPath = context.HttpContext.Server.MapPath("~/Rotativa"); var fileContent = this.CallTheDriver(context); diff --git a/Rotativa/Extensions/ControllerContextExtensions.cs b/Rotativa/Extensions/ControllerContextExtensions.cs index e3e91f8..2992edf 100644 --- a/Rotativa/Extensions/ControllerContextExtensions.cs +++ b/Rotativa/Extensions/ControllerContextExtensions.cs @@ -35,7 +35,7 @@ public static string GetHtmlFromView(this ControllerContext context, ViewEngineR viewResult.View.Render(viewContext, sw); string html = sw.GetStringBuilder().ToString(); - string baseUrl = string.Format("{0}://{1}", HttpContext.Current.Request.Url.Scheme, HttpContext.Current.Request.Url.Authority); + string baseUrl = string.Format("{0}://{1}", context.HttpContext.Request.Url.Scheme, HttpContext.Current.Request.Url.Authority); html = Regex.Replace(html, "", string.Format("", baseUrl), RegexOptions.IgnoreCase); return html; } diff --git a/Rotativa/Rotativa.nuspec b/Rotativa/Rotativa.nuspec index 387966c..26b4bed 100644 --- a/Rotativa/Rotativa.nuspec +++ b/Rotativa/Rotativa.nuspec @@ -2,7 +2,7 @@ Rotativa - 1.7.3 + 1.8.1 Rotativa Giorgio Bozio Giorgio Bozio diff --git a/Rotativa/Rotativa/help-wkhtmltoimage.txt b/Rotativa/Rotativa/help-wkhtmltoimage.txt index f44e514..357326b 100644 --- a/Rotativa/Rotativa/help-wkhtmltoimage.txt +++ b/Rotativa/Rotativa/help-wkhtmltoimage.txt @@ -1,117 +1,33 @@ Name: - wkhtmltoimage 0.12.2.1 (with patched qt) + wkhtmltoimage 0.12.4 (with patched qt) Synopsis: wkhtmltoimage [OPTIONS]... - + Description: - Converts an HTML page into an image, + Converts an HTML page into an image, General Options: - --allow Allow the file or files from the specified - folder to be loaded (repeatable) - --cache-dir Web cache directory - --checkbox-checked-svg Use this SVG file when rendering checked - checkboxes - --checkbox-svg Use this SVG file when rendering unchecked - checkboxes - --cookie Set an additional cookie (repeatable), - value should be url encoded. - --cookie-jar Read and write cookies from and to the - supplied cookie jar file --crop-h Set height for cropping --crop-w Set width for cropping --crop-x Set x coordinate for cropping --crop-y Set y coordinate for cropping - --custom-header Set an additional HTTP header (repeatable) - --custom-header-propagation Add HTTP headers specified by - --custom-header for each resource request. - --no-custom-header-propagation Do not add HTTP headers specified by - --custom-header for each resource request. - --debug-javascript Show javascript debugging output - --no-debug-javascript Do not show javascript debugging output - (default) - --encoding Set the default text encoding, for input -H, --extended-help Display more extensive help, detailing less common command switches -f, --format Output file format --height Set screen height (default is calculated from page content) (default 0) -h, --help Display help - --htmldoc Output program html help - --images Do load or print images (default) - --no-images Do not load or print images - -n, --disable-javascript Do not allow web pages to run javascript - --enable-javascript Do allow web pages to run javascript - (default) - --javascript-delay Wait some milliseconds for javascript - finish (default 200) --license Output license information and exit - --load-error-handling Specify how to handle pages that fail to - load: abort, ignore or skip (default - abort) - --load-media-error-handling Specify how to handle media files - that fail to load: abort, ignore or skip - (default ignore) - --disable-local-file-access Do not allowed conversion of a local file - to read in other local files, unless - explicitly allowed with --allow - --enable-local-file-access Allowed conversion of a local file to read - in other local files. (default) - --manpage Output program man page - --minimum-font-size Minimum font size - --password HTTP Authentication password - --disable-plugins Disable installed plugins (default) - --enable-plugins Enable installed plugins (plugins will - likely not work) - --post Add an additional post field (repeatable) - --post-file Post an additional file (repeatable) - -p, --proxy Use a proxy --quality Output image quality (between 0 and 100) (default 94) -q, --quiet Be less verbose - --radiobutton-checked-svg Use this SVG file when rendering checked - radiobuttons - --radiobutton-svg Use this SVG file when rendering unchecked - radiobuttons - --readme Output program readme - --run-script Run this additional javascript after the - page is done loading (repeatable) - --disable-smart-width Use the specified width even if it is not - large enough for the content - --enable-smart-width Extend --width to fit unbreakable content - (default) - --stop-slow-scripts Stop slow running javascripts (default) - --no-stop-slow-scripts Do not Stop slow running javascripts - --transparent Make the background transparent in pngs - --user-style-sheet Specify a user style sheet, to load with - every page - --username HTTP Authentication username -V, --version Output version information and exit --width Set screen width, note that this is used only as a guide line. Use --disable-smart-width to make it strict. (default 1024) - --window-status Wait until window.status is equal to this - string before rendering page - --zoom Use this zoom factor (default 1) - -Specifying A Proxy: - By default proxy information will be read from the environment variables: - proxy, all_proxy and http_proxy, proxy options can also by specified with the - -p switch - := "http://" | "socks5://" - := (":" )? "@" - := "None" | ? ? (":" )? - - Here are some examples (In case you are unfamiliar with the BNF): - - http://user:password@myproxyserver:8080 - socks5://myproxyserver - None - Contact: - If you experience bugs or want to request new features please visit - - + If you experience bugs or want to request new features please visit + \ No newline at end of file diff --git a/Rotativa/Rotativa/help-wkhtmltopdf.txt b/Rotativa/Rotativa/help-wkhtmltopdf.txt index 686dfc7..cf720d2 100644 --- a/Rotativa/Rotativa/help-wkhtmltopdf.txt +++ b/Rotativa/Rotativa/help-wkhtmltopdf.txt @@ -1,9 +1,9 @@ Name: - wkhtmltopdf 0.12.2.1 (with patched qt) + wkhtmltopdf 0.12.4 (with patched qt) Synopsis: wkhtmltopdf [GLOBAL OPTION]... [OBJECT]... - + Document objects: wkhtmltopdf is able to put several objects into the output file, an object is either a single webpage, a cover webpage or a table of content. The objects @@ -12,14 +12,14 @@ Document objects: options area. Options from the Global Options section can only be placed in the global options area - A page objects puts the content of a singe webpage into the output document. + A page objects puts the content of a single webpage into the output document. (page)? [PAGE OPTION]... Options for the page object can be placed in the global options and the page - options areas. The applicable options can be found in the Page Options and + options areas. The applicable options can be found in the Page Options and Headers And Footer Options sections. - A cover objects puts the content of a singe webpage into the output document, + A cover objects puts the content of a single webpage into the output document, the page does not appear in the table of content, and does not have headers and footers. @@ -47,328 +47,31 @@ Global Options: (default) --no-collate Do not collate when printing multiple copies - --cookie-jar Read and write cookies from and to the - supplied cookie jar file --copies Number of copies to print into the pdf file (default 1) - -d, --dpi Change the dpi explicitly (this has no - effect on X11 based systems) -H, --extended-help Display more extensive help, detailing less common command switches -g, --grayscale PDF will be generated in grayscale -h, --help Display help - --htmldoc Output program html help - --image-dpi When embedding images scale them down to - this dpi (default 600) - --image-quality When jpeg compressing images use this - quality (default 94) --license Output license information and exit -l, --lowquality Generates lower quality pdf/ps. Useful to shrink the result document space - --manpage Output program man page - -B, --margin-bottom Set the page bottom margin - -L, --margin-left Set the page left margin (default 10mm) - -R, --margin-right Set the page right margin (default 10mm) - -T, --margin-top Set the page top margin -O, --orientation Set orientation to Landscape or Portrait (default Portrait) - --page-height Page height -s, --page-size Set paper size to: A4, Letter, etc. (default A4) - --page-width Page width - --no-pdf-compression Do not use lossless compression on pdf - objects -q, --quiet Be less verbose --read-args-from-stdin Read command line arguments from stdin - --readme Output program readme --title The title of the generated pdf file (The title of the first document is used if not specified) -V, --version Output version information and exit -Outline Options: - --dump-default-toc-xsl Dump the default TOC xsl style sheet to - stdout - --dump-outline Dump the outline to a file - --outline Put an outline into the pdf (default) - --no-outline Do not put an outline into the pdf - --outline-depth Set the depth of the outline (default 4) - Page Options: - --allow Allow the file or files from the specified - folder to be loaded (repeatable) - --background Do print background (default) - --no-background Do not print background - --cache-dir Web cache directory - --checkbox-checked-svg Use this SVG file when rendering checked - checkboxes - --checkbox-svg Use this SVG file when rendering unchecked - checkboxes - --cookie Set an additional cookie (repeatable), - value should be url encoded. - --custom-header Set an additional HTTP header (repeatable) - --custom-header-propagation Add HTTP headers specified by - --custom-header for each resource request. - --no-custom-header-propagation Do not add HTTP headers specified by - --custom-header for each resource request. - --debug-javascript Show javascript debugging output - --no-debug-javascript Do not show javascript debugging output - (default) - --default-header Add a default header, with the name of the - page to the left, and the page number to - the right, this is short for: - --header-left='[webpage]' - --header-right='[page]/[toPage]' --top 2cm - --header-line - --encoding Set the default text encoding, for input - --disable-external-links Do not make links to remote web pages - --enable-external-links Make links to remote web pages (default) - --disable-forms Do not turn HTML form fields into pdf form - fields (default) - --enable-forms Turn HTML form fields into pdf form fields - --images Do load or print images (default) - --no-images Do not load or print images - --disable-internal-links Do not make local links - --enable-internal-links Make local links (default) - -n, --disable-javascript Do not allow web pages to run javascript - --enable-javascript Do allow web pages to run javascript - (default) - --javascript-delay Wait some milliseconds for javascript - finish (default 200) - --load-error-handling Specify how to handle pages that fail to - load: abort, ignore or skip (default - abort) - --load-media-error-handling Specify how to handle media files - that fail to load: abort, ignore or skip - (default ignore) - --disable-local-file-access Do not allowed conversion of a local file - to read in other local files, unless - explicitly allowed with --allow - --enable-local-file-access Allowed conversion of a local file to read - in other local files. (default) - --minimum-font-size Minimum font size - --exclude-from-outline Do not include the page in the table of - contents and outlines - --include-in-outline Include the page in the table of contents - and outlines (default) - --page-offset Set the starting page number (default 0) - --password HTTP Authentication password - --disable-plugins Disable installed plugins (default) - --enable-plugins Enable installed plugins (plugins will - likely not work) - --post Add an additional post field (repeatable) - --post-file Post an additional file (repeatable) --print-media-type Use print media-type instead of screen --no-print-media-type Do not use print media-type instead of screen (default) - -p, --proxy Use a proxy - --radiobutton-checked-svg Use this SVG file when rendering checked - radiobuttons - --radiobutton-svg Use this SVG file when rendering unchecked - radiobuttons - --run-script Run this additional javascript after the - page is done loading (repeatable) - --disable-smart-shrinking Disable the intelligent shrinking strategy - used by WebKit that makes the pixel/dpi - ratio none constant - --enable-smart-shrinking Enable the intelligent shrinking strategy - used by WebKit that makes the pixel/dpi - ratio none constant (default) - --stop-slow-scripts Stop slow running javascripts (default) - --no-stop-slow-scripts Do not Stop slow running javascripts - --disable-toc-back-links Do not link from section header to toc - (default) - --enable-toc-back-links Link from section header to toc - --user-style-sheet Specify a user style sheet, to load with - every page - --username HTTP Authentication username - --viewport-size <> Set viewport size if you have custom - scrollbars or css attribute overflow to - emulate window size - --window-status Wait until window.status is equal to this - string before rendering page - --zoom Use this zoom factor (default 1) - -Headers And Footer Options: - --footer-center Centered footer text - --footer-font-name Set footer font name (default Arial) - --footer-font-size Set footer font size (default 12) - --footer-html Adds a html footer - --footer-left Left aligned footer text - --footer-line Display line above the footer - --no-footer-line Do not display line above the footer - (default) - --footer-right Right aligned footer text - --footer-spacing Spacing between footer and content in mm - (default 0) - --header-center Centered header text - --header-font-name Set header font name (default Arial) - --header-font-size Set header font size (default 12) - --header-html Adds a html header - --header-left Left aligned header text - --header-line Display line below the header - --no-header-line Do not display line below the header - (default) - --header-right Right aligned header text - --header-spacing Spacing between header and content in mm - (default 0) - --replace Replace [name] with value in header and - footer (repeatable) - -TOC Options: - --disable-dotted-lines Do not use dotted lines in the toc - --toc-header-text The header text of the toc (default Table - of Contents) - --toc-level-indentation For each level of headings in the toc - indent by this length (default 1em) - --disable-toc-links Do not link from toc to sections - --toc-text-size-shrink For each level of headings in the toc the - font is scaled by this factor (default - 0.8) - --xsl-style-sheet Use the supplied xsl style sheet for - printing the table of content - -Page sizes: - The default page size of the rendered document is A4, but using this - --page-size optionthis can be changed to almost anything else, such as: A3, - Letter and Legal. For a full list of supported pages sizes please see - . - - For a more fine grained control over the page size the --page-height and - --page-width options may be used - -Reading arguments from stdin: - If you need to convert a lot of pages in a batch, and you feel that - wkhtmltopdf is a bit to slow to start up, then you should try - --read-args-from-stdin, - - When --read-args-from-stdin each line of input sent to wkhtmltopdf on stdin - will act as a separate invocation of wkhtmltopdf, with the arguments specified - on the given line combined with the arguments given to wkhtmltopdf - - For example one could do the following: - - echo "http://qt-project.org/doc/qt-4.8/qapplication.html qapplication.pdf" >> cmds - echo "cover google.com http://en.wikipedia.org/wiki/Qt_(software) qt.pdf" >> cmds - wkhtmltopdf --read-args-from-stdin --book < cmds - -Specifying A Proxy: - By default proxy information will be read from the environment variables: - proxy, all_proxy and http_proxy, proxy options can also by specified with the - -p switch - - := "http://" | "socks5://" - := (":" )? "@" - := "None" | ? ? (":" )? - - Here are some examples (In case you are unfamiliar with the BNF): - - http://user:password@myproxyserver:8080 - socks5://myproxyserver - None - -Footers And Headers: - Headers and footers can be added to the document by the --header-* and - --footer* arguments respectfully. In header and footer text string supplied - to e.g. --header-left, the following variables will be substituted. - - * [page] Replaced by the number of the pages currently being printed - * [frompage] Replaced by the number of the first page to be printed - * [topage] Replaced by the number of the last page to be printed - * [webpage] Replaced by the URL of the page being printed - * [section] Replaced by the name of the current section - * [subsection] Replaced by the name of the current subsection - * [date] Replaced by the current date in system local format - * [isodate] Replaced by the current date in ISO 8601 extended format - * [time] Replaced by the current time in system local format - * [title] Replaced by the title of the of the current page object - * [doctitle] Replaced by the title of the output document - * [sitepage] Replaced by the number of the page in the current site being converted - * [sitepages] Replaced by the number of pages in the current site being converted - - - As an example specifying --header-right "Page [page] of [toPage]", will result - in the text "Page x of y" where x is the number of the current page and y is - the number of the last page, to appear in the upper left corner in the - document. - - Headers and footers can also be supplied with HTML documents. As an example - one could specify --header-html header.html, and use the following content in - header.html: - - - - - - - -
- Page of -
- - - - As can be seen from the example, the arguments are sent to the header/footer - html documents in get fashion. - -Outlines: - Wkhtmltopdf with patched qt has support for PDF outlines also known as book - marks, this can be enabled by specifying the --outline switch. The outlines - are generated based on the tags, for a in-depth description of how this - is done see the Table Of Contest section. - - The outline tree can sometimes be very deep, if the tags where spread to - generous in the HTML document. The --outline-depth switch can be used to - bound this. - -Table Of Content: - A table of content can be added to the document by adding a toc object to the - command line. For example: - - wkhtmltopdf toc http://qt-project.org/doc/qt-4.8/qstring.html qstring.pdf - - The table of content is generated based on the H tags in the input documents. - First a XML document is generated, then it is converted to HTML using XSLT. - - The generated XML document can be viewed by dumping it to a file using the - --dump-outline switch. For example: - - wkhtmltopdf --dump-outline toc.xml http://qt-project.org/doc/qt-4.8/qstring.html qstring.pdf - - The XSLT document can be specified using the --xsl-style-sheet switch. For - example: - - wkhtmltopdf toc --xsl-style-sheet my.xsl http://qt-project.org/doc/qt-4.8/qstring.html qstring.pdf - - The --dump-default-toc-xsl switch can be used to dump the default XSLT style - sheet to stdout. This is a good start for writing your own style sheet - - wkhtmltopdf --dump-default-toc-xsl - The XML document is in the namespace "http://wkhtmltopdf.org/outline" it has a - root node called "outline" which contains a number of "item" nodes. An item - can contain any number of item. These are the outline subsections to the - section the item represents. A item node has the following attributes: - - * "title" the name of the section. - * "page" the page number the section occurs on. - * "link" a URL that links to the section. - * "backLink" the name of the anchor the the section will link back to. - - The remaining TOC options only affect the default style sheet so they will not - work when specifying a custom style sheet. Contact: - If you experience bugs or want to request new features please visit - - + If you experience bugs or want to request new features please visit + \ No newline at end of file diff --git a/Rotativa/Rotativa/wkhtmltoimage.exe b/Rotativa/Rotativa/wkhtmltoimage.exe index d44e52c..e26e1ac 100644 Binary files a/Rotativa/Rotativa/wkhtmltoimage.exe and b/Rotativa/Rotativa/wkhtmltoimage.exe differ diff --git a/Rotativa/Rotativa/wkhtmltopdf.exe b/Rotativa/Rotativa/wkhtmltopdf.exe index 0002e8a..af5283b 100644 Binary files a/Rotativa/Rotativa/wkhtmltopdf.exe and b/Rotativa/Rotativa/wkhtmltopdf.exe differ diff --git a/Rotativa/ViewAsImage.cs b/Rotativa/ViewAsImage.cs index 7efd08b..9c37658 100644 --- a/Rotativa/ViewAsImage.cs +++ b/Rotativa/ViewAsImage.cs @@ -75,7 +75,7 @@ protected override byte[] CallTheDriver(ControllerContext context) ViewEngineResult viewResult = GetView(context, viewName, MasterName); string html = context.GetHtmlFromView(viewResult, viewName, Model); - byte[] fileContent = WkhtmltoimageDriver.ConvertHtml(this.WkhtmlPath, this.GetConvertOptions(), html); + byte[] fileContent = WkhtmltoimageDriver.ConvertHtml(this.WkhtmlPath, this.GetConvertOptions(), html, timeout: ConvertTimeout); return fileContent; } } diff --git a/Rotativa/ViewAsPdf.cs b/Rotativa/ViewAsPdf.cs index 078178a..4bcc11e 100644 --- a/Rotativa/ViewAsPdf.cs +++ b/Rotativa/ViewAsPdf.cs @@ -75,7 +75,7 @@ protected override byte[] CallTheDriver(ControllerContext context) ViewEngineResult viewResult = GetView(context, viewName, MasterName); string html = context.GetHtmlFromView(viewResult, viewName, Model); - byte[] fileContent = WkhtmltopdfDriver.ConvertHtml(this.WkhtmlPath, this.GetConvertOptions(), html); + byte[] fileContent = WkhtmltopdfDriver.ConvertHtml(this.WkhtmlPath, this.GetConvertOptions(), html, ConvertTimeout); return fileContent; } } diff --git a/Rotativa/WkhtmlDriver.cs b/Rotativa/WkhtmlDriver.cs index a733e91..3bfff39 100644 --- a/Rotativa/WkhtmlDriver.cs +++ b/Rotativa/WkhtmlDriver.cs @@ -2,6 +2,8 @@ using System.Diagnostics; using System.IO; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace Rotativa { @@ -15,7 +17,33 @@ public abstract class WkhtmlDriver /// String containing HTML code that should be converted to PDF. /// /// PDF as byte array. - protected static byte[] Convert(string wkhtmlPath, string switches, string html, string wkhtmlExe) + protected static byte[] Convert(string wkhtmlPath, string switches, string html, string wkhtmlExe, int? timeout = null) + { + if (!timeout.HasValue) + { + return ConvertExecute(wkhtmlPath, ref switches, ref html, wkhtmlExe); + } + else + { + var cancellationTokenSource = new CancellationTokenSource(); + var task = Task.Run(() => ConvertExecute(wkhtmlPath, ref switches, ref html, wkhtmlExe, cancellationTokenSource.Token)); + task.ConfigureAwait(false); + if (!task.Wait(timeout.Value)) + { + cancellationTokenSource.Cancel(); + throw new TimeoutException($"Timeout in converting given URL or HTML string to PDF after {timeout.Value}"); + } + + if (task.IsFaulted) + { + throw task.Exception ?? new Exception("Failed in converting given URL or HTML string to PDF"); + } + + return task.Result; + } + } + + private static byte[] ConvertExecute(string wkhtmlPath, ref string switches, ref string html, string wkhtmlExe, CancellationToken cancellationToken = default) { // switches: // "-q" - silent output, only errors - no progress messages @@ -44,6 +72,16 @@ protected static byte[] Convert(string wkhtmlPath, string switches, string html, CreateNoWindow = true } }; + cancellationToken.Register(() => + { + try + { + if (!proc.HasExited) + proc.Kill(); + } + catch { } + }); + proc.Start(); // generate PDF from given HTML string, not from URL @@ -70,7 +108,7 @@ protected static byte[] Convert(string wkhtmlPath, string switches, string html, string error = proc.StandardError.ReadToEnd(); - if (ms.Length == 0) + if (ms.Length == 0 || !string.IsNullOrEmpty(error) || proc.ExitCode > 0) { throw new Exception(error); } diff --git a/Rotativa/WkhtmltoimageDriver.cs b/Rotativa/WkhtmltoimageDriver.cs index 0196abc..64e74cd 100644 --- a/Rotativa/WkhtmltoimageDriver.cs +++ b/Rotativa/WkhtmltoimageDriver.cs @@ -11,9 +11,9 @@ public class WkhtmltoimageDriver : WkhtmlDriver /// Switches that will be passed to wkhtmltopdf binary. /// String containing HTML code that should be converted to PDF. /// PDF as byte array. - public static byte[] ConvertHtml(string wkhtmltopdfPath, string switches, string html) + public static byte[] ConvertHtml(string wkhtmltopdfPath, string switches, string html, int? timeout = null) { - return Convert(wkhtmltopdfPath, switches, html, wkhtmlExe); + return Convert(wkhtmltopdfPath, switches, html, wkhtmlExe, timeout: timeout); } /// @@ -22,9 +22,9 @@ public static byte[] ConvertHtml(string wkhtmltopdfPath, string switches, string /// Path to wkthmltopdf. /// Switches that will be passed to wkhtmltopdf binary. /// PDF as byte array. - public static byte[] Convert(string wkhtmltopdfPath, string switches) + public static byte[] Convert(string wkhtmltopdfPath, string switches, int? timeout = null) { - return Convert(wkhtmltopdfPath, switches, null, wkhtmlExe); + return Convert(wkhtmltopdfPath, switches, null, wkhtmlExe, timeout: timeout); } } } \ No newline at end of file diff --git a/Rotativa/WkhtmltopdfDriver.cs b/Rotativa/WkhtmltopdfDriver.cs index 96522a3..b91b4ad 100644 --- a/Rotativa/WkhtmltopdfDriver.cs +++ b/Rotativa/WkhtmltopdfDriver.cs @@ -11,9 +11,9 @@ public class WkhtmltopdfDriver : WkhtmlDriver /// Switches that will be passed to wkhtmltopdf binary. /// String containing HTML code that should be converted to PDF. /// PDF as byte array. - public static byte[] ConvertHtml(string wkhtmltopdfPath, string switches, string html) + public static byte[] ConvertHtml(string wkhtmltopdfPath, string switches, string html, int? timeout = null) { - return Convert(wkhtmltopdfPath, switches, html, wkhtmlExe); + return Convert(wkhtmltopdfPath, switches, html, wkhtmlExe, timeout: timeout); } /// @@ -22,9 +22,9 @@ public static byte[] ConvertHtml(string wkhtmltopdfPath, string switches, string /// Path to wkthmltopdf. /// Switches that will be passed to wkhtmltopdf binary. /// PDF as byte array. - public static byte[] Convert(string wkhtmltopdfPath, string switches) + public static byte[] Convert(string wkhtmltopdfPath, string switches, int? timeout = null) { - return Convert(wkhtmltopdfPath, switches, null, wkhtmlExe); + return Convert(wkhtmltopdfPath, switches, null, wkhtmlExe, timeout: timeout); } } }