How to use Bryntum PDF Export server with Microsoft IIS server

November 01, 2023

This small how to is targeted at people who want to run the Bryntum https://github.com/bryntum/pdf-export-server with IIS server if they host it’s JS part in for example ASP.NET application.

So lets break down possible problems I was able to troubleshoot.

How to run the server

First problem you might encounter is that the PDF export server will run under NodeJS 16 for now. I asked (here https://github.com/bryntum/pdf-export-server/issues/16) if they will try to update to the latest LTS but… not response yet. You can try to update on your own since the code is public but that is extra work.

Quick fix (to force NodeJS v16 on your prod machine) is to download portable NodeJS from https://nodejs.org/dist/ and take one for your machine with .zip extension which is version you will not need to install (I like to call them portable since there is this page I like https://portableapps.com/)

With this version placed somewhere on your machine can can do this :

nodejs.exe "path/to/PdfExportServer/src/server.js"

which will start the server for you on http://localhost:8080. For further information on cmd line arguments refer to the documentation here: https://github.com/bryntum/pdf-export-server/blob/main/docs/configuration.md

Authentication

It is a good idea to create a reverse proxy to not directly connect to the PDF export server but rather have a URL where you can access it like a part of your application. Then the requests also need to be authenticated. Since there is auth built in the PDF export server, it will not by default send cookies to IIS thus the request to working URL (from browser which automatically sends cookies) so you might have 401 error.

Documentation to the rescue : https://bryntum.com/products/gantt/docs/api/Grid/feature/export/PdfExport#config-fetchOptions which boils down to sending this prop

fetchOptions: { credentials: "same-origin" }

There is JSON payload sent back and rendered PDF fails to download

The HTTP POST that sends data to render as PDF gets just JSON response with link to the PDF export server to download the rendered response. It unfortunately seems that this will always be rendered with name of server https://github.com/bryntum/pdf-export-server/blob/main/src/server/WebServer.js#L109 and I was not able to get around this differently than to use another prop and get PDF as response with help of this property : https://bryntum.com/products/gantt/docs/api/Grid/feature/export/PdfExport#config-sendAsBinary which again translate to this

sendAsBinary: true

You might need to tweak the maximum accepted payload and number of workers

This really depends on how you will be using Bryntum Scheduler or any other lib that renders PDF but it seems on our end we had to tweak the default of 50MB incoming payload and default number of workers which ended up on our side to be 10 for both not killing the machine where PDF export server is running and also making the PDF faster from defaults. Both are cmd line properties like this

--maximum=100mb --max-workers=10

How to start the PDF export server as Windows service

To not start the PDF export server by hand you might want to automate things a bit. You can take the Docker image from https://hub.docker.com/r/bryntum/pdf-export-server or alternatively use this package https://github.com/coreybutler/node-windows and create service. These are the samples to install and uninstall the service with node-windows.

install script (execPath is just sample here)

var Service = require('node-windows').Service;

// Create a new service object
var svc = new Service({
  name:'Bryntum PDF Export Server',
  description: 'Starts NodeJS PDF export server for Bryntum charts',
  script: 'src\\server.js',
  scriptOptions: '--maximum=100mb --max-workers=10',
  execPath: 'c:\\Program Files\\iisnode\\node-v16\\node.exe',
  nodeOptions: [
    '--harmony',
    '--max_old_space_size=4096'
  ]
  //, workingDirectory: '...'
  //, allowServiceLogon: true
});

// Listen for the "install" event, which indicates the
// process is available as a service.
svc.on('install',function(){
  svc.start();
});

svc.install();

uninstall script

var Service = require('node-windows').Service;

// Create a new service object
var svc = new Service({
  name:'Bryntum PDF Export Server',
  script: 'src\\server.js',
});

// Listen for the "uninstall" event so we know when it's done.
svc.on('uninstall',function(){
  console.log('Uninstall complete.');
  console.log('The service exists: ',svc.exists);
});

// Uninstall the service.
svc.uninstall();

How to reverse proxy under IIS

This one was simple , you need to install https://www.iis.net/downloads/microsoft/application-request-routing. Stupid thing is that you might end up with some errors. First of all settings where to redirect are done in web.config file and since at the bottom of the page above there is small tiny note

ARR depends on URL Rewrite. Ensure URL Rewrite is installed prior to installing ARR.

you might by accident end up in situation where some devs already have https://www.iis.net/downloads/microsoft/url-rewrite installed and changes in web.config will not do anything but without ARR things will not work as expected. But some ppl might end up with yellow screen of death without knowing why. So please beware of this.

Also you might end up seeing something like 502.3 error which turned out to be ARR switched off

IIS ARR switched off by error

I recommend looking here https://github.com/MicrosoftDocs/iis-docs/blob/main/iis/extensions/configuring-application-request-routing-arr/creating-a-forward-proxy-using-application-request-routing.md#user-content-configure-arr-as-a-forward-proxy where MS ppl made quite nice “how to”.

PDF server will not render all resources on the page

It might happen that you will be missing some colors of boxes (missing css) or some arrows down or different special chars (missing fonts) from the page you just sent to be exported.

If you take a look at the logs subfolder in the pdf export server folder you might find something like :

2023-11-24T10:54:56.579Z error: [Worker@pm2esxbmio3z47k1i4dzn] Page 1/1 reports: Access to font at 'http://localhost/Theme/_fonts/Roboto-Regular.woff' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. location: about:blank

this does not give much sense since on my page there is no iframe or anything that could have about:blank. It seems that this is the state of the chromium when it is started.

The part No 'Access-Control-Allow-Origin' header is present on the requested resource is OK, I am not sending any CORS headers.

OK so lets send correct ‘Access-Control-Allow-Origin’. Will this be the thing? Again nope, I did it and got error like this :

Page 1/1 reports: Access to font at 'http://myhostnamehere/Theme/_fonts/Roboto-Regular.woff' from origin 'null' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'http://myhostnamehere' that is not equal to the supplied origin. location: about:blank

I checked on in my devtools when the page is loading, nope the origin is not null.

OK, now what? How about setting ‘Access-Control-Allow-Origin’ to *. That worked, but our security team was not happy about it (hi guys, I understand).

Reading the docs here https://github.com/bryntum/pdf-export-server/blob/main/docs/configuration.md I was thinking “only if I could send --disable-web-security to the chromium… so I investigated in the sources and… https://github.com/bryntum/pdf-export-server/blob/main/src/server.js#L49

if (config['disable-web-security']) {
    chromiumArgs.push('--disable-web-security');
}

this looks promissing… why not give it a try and yep, this is what I needed. The PDF now looks like expected.

So if I would like to fix this error in the install script above it might look like this :

var Service = require('node-windows').Service;

// Create a new service object
var svc = new Service({
  name:'Bryntum PDF Export Server',
  description: 'Starts NodeJS PDF export server for Bryntum charts',
  script: 'src\\server.js',
  scriptOptions: '--maximum=100mb --max-workers=10 --disable-web-security',
  execPath: 'c:\\Program Files\\iisnode\\node-v16\\node.exe',
  nodeOptions: [
    '--harmony',
    '--max_old_space_size=4096'
  ]
  //, workingDirectory: '...'
  //, allowServiceLogon: true
});

// Listen for the "install" event, which indicates the
// process is available as a service.
svc.on('install',function(){
  svc.start();
});

svc.install();

Hope you saved some time.


Profile picture

Written by Dušan Roštár - the "mr edge case" guy
my twitter : rostacik, my linkedin : rostar, drop me an email : here