Running Node.js Web Apps on IIS with HttpPlatformHandler

June 11, 2022

When Microsoft developed HttpPlatformHandler more than a decade ago to enable non-Microsoft web technologies on Windows/IIS, they didn’t know that one day

  • Microsoft can embrace Linux in Azure
  • Some Microsoft users stick to IIS with their Java/Python/Node.js/Go applications.

Thus, HttpPlatformHandler still plays an important role in the ecosystem and won’t go away easily. However, the landscape keeps evolving so this post tries to capture some latest changes on Node.js and show you how to proper set up everything needed and more critically how to troubleshoot if issues occur.

Basic Node.js Setup

No doubt we will start from a sample application as below,

import { createServer } from 'http';

const port = process.env.PORT || 3000;

const requestListener = function(req, res) {
  res.writeHead(200);
  res.end('My first server');
}

const server = createServer(requestListener);
server.listen(port, function () {
  console.log('Example app listening on port ' + port + '!');
});

If we save it as C:\node-test\app.js, then on a Windows machine with Node.js installed, a simple command node app.js can launch the application at port 3000,

PS C:\node-test> node app.js
Example app listening on port 3000!

HttpPlatformHandler Setup

Now let’s download and install HttpPlatformHandler on IIS, and add a web.config in C:\node-test,

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <handlers>
            <add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" requireAccess="Script" />
        </handlers>
        <httpPlatform stdoutLogEnabled="true" stdoutLogFile=".\node.log" startupTimeLimit="20" processPath="C:\Program Files\nodejs\node.exe" arguments=".\app.js">
            <environmentVariables>
                <environmentVariable name="PORT" value="%HTTP_PLATFORM_PORT%" />
                <environmentVariable name="NODE_ENV" value="Production" />
            </environmentVariables>
        </httpPlatform>
    </system.webServer>
</configuration>

With all settings in place, I can go back to IIS Manager and create a site (I chose *:8099 as site binding) to point to C:\node-test. By opening a web browser and navigate to http://localhost:8099/, I can see “My first server” as expected.

Troubleshooting

0x80070002

But wait! Why did I see an error page saying “HTTP Error 502.3 - Bad Gateway” with Error Code 0x80070002?

Figure 1: Bad Gateway error page of 0x80070002

If you are very familiar with Windows error code, then you know that this error indicates “file not found”,

> .\Err.exe 80070002
# No results found for hex 0x4c5c572 / decimal 80070002
# for hex 0x80070002 / decimal -2147024894
  COR_E_FILENOTFOUND                                             corerror.h
  DIERR_NOTFOUND                                                 dinput.h
  DIERR_OBJECTNOTFOUND                                           dinput.h
  STIERR_OBJECTNOTFOUND                                          stierr.h
  DRM_E_WIN32_FILE_NOT_FOUND                                     windowsplayready.h
  E_FILE_NOT_FOUND                                               wpc.h
# as an HRESULT: Severity: FAILURE (1), FACILITY_NTWIN32 (0x7), Code 0x2
# for hex 0x2 / decimal 2
  STATUS_WAIT_2                                                  ntstatus.h
# as an HRESULT: Severity: FAILURE (1), FACILITY_WIN32 (0x7), Code 0x2
  ERROR_FILE_NOT_FOUND                                           winerror.h
# The system cannot find the file specified.
# 8 matches found for "80070002"

So which part of our settings can trigger this error? Luckily our first attempt on C:\Program Files\nodejs\node.exe seems to raise a red flag,

Figure 2: Node.js Windows shortcut

As the image shows, the Node.js installer creates a shortcut here, so that C:\Program Files\nodejs\node.exe is in fact C:\Users\<user name>\AppData\Roaming\nvm\v16.13.2\node.exe on this machine.

Note that the shortcut on your machine might point to a different location.

Due to the design of IIS, such shortcuts do not work very well, so we need to change web.config as below here,

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <handlers>
            <add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" requireAccess="Script" />
        </handlers>
        <httpPlatform stdoutLogEnabled="true" stdoutLogFile=".\node.log" startupTimeLimit="20" processPath="C:\Users\<user name>\AppData\Roaming\nvm\v16.13.2\node.exe" arguments=".\app.js">
            <environmentVariables>
                <environmentVariable name="PORT" value="%HTTP_PLATFORM_PORT%" />
                <environmentVariable name="NODE_ENV" value="Production" />
            </environmentVariables>
        </httpPlatform>
    </system.webServer>
</configuration>

0x80070005

OK, now refreshing the browser gives us a different Bad Gateway error page because its Error Code changes to 0x80070005,

Figure 3: Bad Gateway error page of 0x80070005
> .\Err.exe 80070005
# No results found for hex 0x4c5c575 / decimal 80070005
# for hex 0x80070005 / decimal -2147024891
  COR_E_UNAUTHORIZEDACCESS                                       corerror.h
# Access is denied.
  DIERR_OTHERAPPHASPRIO                                          dinput.h
  DIERR_READONLY                                                 dinput.h
  DIERR_HANDLEEXISTS                                             dinput.h
  DSERR_ACCESSDENIED                                             dsound.h
  STIERR_READONLY                                                stierr.h
  STIERR_NOTINITIALIZED                                          stierr.h
  E_ACCESSDENIED                                                 winerror.h
# General access denied error
# as an HRESULT: Severity: FAILURE (1), FACILITY_WIN32 (0x7), Code 0x5
# for hex 0x5 / decimal 5
  ERROR_ACCESS_DENIED                                            winerror.h
# Access is denied.
# 9 matches found for "80070005"

This also makes perfect sense, because anything under C:\Users\<user name>\ is protected and accessible only by that user account by default, not IIS_IUSRS.

The End

Once I grant IIS_IUSRS read access to C:\Users\<user name>\AppData\Roaming\nvm\v16.13.2\node.exe, the browser starts to work as expected and gives me “My first server”.

With Process Explorer I can further analyze the node.exe process spin off by w3wp.exe,

Figure 4: Node.js process under IIS

And if an application pool recycle is triggered in IIS Manager, I can also observe the two processes being shut down peacefully.

Note that the peaceful shutdown is via Windows API OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId). You can read ASP.NET Core module source code to learn more, as this module is derived from HttpPlatformHandler.

Sidenote

One thing you might notice is that I wrote a very simple Node.js application as example. Why not go a little bit further to use a framework like Express for Node.js? In fact I did try it out (Express 4.18.1), but node.exe does not shut down peacefully and it also blocks w3wp.exe from proper shutdown. I didn’t have time to dig further there, but you might find it an interesting field to explore. Good luck.

All rights reserved. © Lex Li, 2005-2022