Friday, November 4, 2011

Debug node.js applications on Windows with iisnode integrated debugging

The iisnode project allows hosting node.js applications in IIS on Windows. As of version 0.1.9, iisnode includes fully integrated debugging experience based on the excellent node-inspector debugger by Danny Coates.

image

These are the benefits of the integrated debugging in iisnode:

  • No-config deployment on Windows: host your node.js application in IIS on Windows using iisnode and you are debug-ready. No special configuration, no additional packages required to enable debugging. 
  • Cross-platform debugging: access the application as well as the debugger for that application from a browser on any platform (Windows, Mac, Linux, Unix). No client side tools required beyond a WebKit-based web browser.
  • Debug after deployment, even in shared hosting environments: both the application and the debugger are exposed on a single port number over HTTP, typically 80. Application and debugger do not require separate externally visible ports, and you don’t need access to the machine to start the debugger process.
  • Traverse firewalls and proxies: HTTP long polling based protocol (as opposed to websockets) offers the most firewall- and proxy-friendly notification mechanism for snappy debugging experience.

Let’s have a closer look.

Getting started

For this walkthrough, this is what you need:

  1. Windows machine with IIS 7.x installed
  2. Install node.js v0.5.10 or greater (important: iisnode debugging does not work with node.js versions < v0.5.10)
  3. Install iisnode for IIS 7.x for x86 or x64, depending on your OS.
  4. Set up iisnode samples by calling “%programfiles%\iisnode\setupsamples.bat” with administrative privileges.
  5. You need a WebKit enabled web browser. Google Chrome or Safari are some of the choices on Windows. On a Mac as you are set up with Safari or Chrome. iisnode debugging does not currently work in IE or Firefox.

You can also leverage the debugging functionality of iisnode when developing in WebMatrix and IIS Express. Install iisnode for WebMatrix from here. Read more about WebMatrix development with iisnode here.

Basic debugging scenario

Navigate to the iisnode samples at http://localhost/node in your WebKit-based browser, then choose one of the samples (say helloworld). Notice the two links pointing to the node.js application itself and the debugger for that application:

image

Click on the link for debugging which will open a new browser window or tab and take you to http://localhost/node/helloworld/app.js/debug. That URL serves up the node-inspector UI. Behind the scenes iisnode started both the helloworld node.js application and the node-inspector debugger and connected them:

image

Go ahead and put a breakpoint within the http callback in line 4. Then open a new browser window and navigate to the actual application at http://localhost/node/hello.js. You will notice the browser appears to be waiting for the server response:

image

This is because your server side application has now stopped on the breakpoint you set:

image

From here, you can use the debugger window to inspect variables, the call stack, evaluate expressions in real time, step through the code (even 3rd party modules and node.js code your application is using) etc. For example, you can easily inspect the request headers of the HTTP request just received using the locals window of the console:

image

When you are done debugging the application, you need to terminate the debugging session by navigating to http://localhost/node/helloworld/hello.js/debug?kill. This command will cause iisnode to terminate the debugee and the debugger. Subsequent application requests will cause activation of a new application instance started in a regular, non-debug mode (which is more lightweight):

image

A debugging pattern I found convenient is to have 3 browser windows opened at the same time: one for the application, one for debugger UI, and one for the ?kill debugging command. It is then easy to switch between the three windows to initiate application requests, manipulate the debugger, or terminate the debugging session (perhaps to start fresh) by simply refreshing the respective browser windows:

image

Note that refreshing the window with the http://localhost/node/helloworld/hello.js/?kill URL requires that you first navigate away from the node-inspector UI in the other window, for reasons explained in the next section.

Understanding the debugger interactions

To effectively use the iisnode integrated debugging, it is important to understand that the debugger maintains server side state and how that state is managed. The state machine below shows server side state transitions in response to different HTTP requests iisnode receives; the pragmatic essence of this is the following:

  • Closing and reopening the browser does not affect server side state. In particular server does not attempt to detect and react to the client closing the browser window. That means you can start debugging in one window, close it, open another browser window, navigate to /app.js/debug, and continue where you left off – all breakpoints are preserved. You can also refresh your browser while debugging – you should end up in the same place you left.
  • Issuing /app.js/debug?kill request is a great way to get started with a clean slate – the server kills the app.js application regardless if it is running in the debug or regular mode, as well as the debugger (if running) and puts you in the initial state. One word of caution: when issuing /app.js/debug?kill request, make sure no other browser window is opened with the debugger UI – the debugger is continuously issuing HTTP long polling requests which would cause the server to transition back to the debugging state right after the debugger has been killed by the /app.js/debug?kill request.
  • Refreshing a browser pointing at /app.js/debug[?brk] does not recycle the application if it is already running in debug mode. This means no debugging state of an already debugged application is lost. In particular, all breakpoints are preserved.
  • Issuing /app.js/debug[?brk] request will kill the application if it is running in the regular mode, and restart it in debug mode. If the application is already running in debug mode when this request is received, it is not terminated and restarted, but simply attached to. 
  • Using the three browser windows shown above is a great way to fully control the server side state by simply refreshing the browser windows.

image

Fine tuning

While the basic debugging scenario in iisnode works out of the box, there are a few options one can configure in the web.config file of the web application to fine tune the experience. They are controlled with the following configuration options, shown below with their default values:

<configuration>
<system.webServer>
<iisnode
debuggerPortRange="5058-6058"
debuggerPathSegment="debug"
maxNamedPipeConnectionRetry="3"
namedPipeConnectionRetryDelay="2000"
/>
</system.webServer>
</configuration>

The debuggerPortRange is the range of TCP ports iisnode will use to set up communication between the node-inspector debugger and the debugee. iisnode only picks up those ports from this range that are not in use. iisnode uses round-robin logic to assign TCP ports from this range for consecutive debugging sessions.

Integrated debugging in iisnode requires a part of the URL space of the application to be dedicated to interactions with the debugger. The debuggerPathSegment provides control over the URL segment following the node.js application name in the HTTP request URL that the iisnode debugger will take over. For example, given a node.js application available at http://foo.com/bar/baz.js, the iisnode debugger will intercept and process all HTTP requests sent to http://foo.com/bar/baz.js/{debuggerPathSegment} (by default http://foo.com/bar/baz.js/debug) and all subordinate URLs. Note that the application itself will typically be reachable using URLs that do not contain the *.js file name in them, since in most situations one will want to leverage the IIS URL rewrite module to customize the URL space of node.js the application.

The debuggerPathSegment setting can also be used to obfuscate access to the debugger as a low key approach to securing your service. When the debuggerPathSegment is set to a cryptographically random value (or even a GUID), only folks with knowledge of that string can access the debugger.

The iisnode debugger re-uses the maxNamedPipeConnectionRetry and namedPipeConnectionRetryDelay settings used in non-debug scenarios to control the initialization of the debugger and debugee. The debugee process is expected to start listening on the debugging TCP port shortly after startup. iisnode will ensure the debugger is only started after the debugee is ready to accept TCP connections on the debugging port. iisnode does it by starting the debugee process, trying to connect to the TCP debugging port itself, and launching the debugger only after a successful connect attempt. The maxNamedPipeConnectionRetry controls the number of connection attempts to the TCP debugging port of the debugee that iisnode will perform, and the namedPipeConnectionRetryDelay controls the time in milliseconds between two consecutive attempts.

Under the hood

The integrated debugging in iisnode is based on the node-inspector debugger by Danny Coates. This is a high level picture of how a typical non-iisnode node-inspector setup works that will help explain the changes that were necessary to integrate it into iisnode:

image

In this standard configuration, which Glenn Block showed how you can set up on Windows, node-inspector and the debugged application are running as two separate processes on the same machine. They communicate with each other using the V8 debugging protocol over a local TCP connection. The debugged application would typically expose an HTTP endpoint over a specific port Z that browser clients can connect to. The node-inspector instance will expose two endpoints: one HTTP endpoint over port X (different than Z) for the HTML interactions with the browser over which node-inspector UI and other static content is obtained, and a websocket endpoints over port Y that is used to relay the V8 debugging protocol communication to the browser. node-inspector uses socket.io to abstract away that duplex communication channel. In this configuration, node-inspector application itself is responsible for serving static content (HTML files, JPG images, client side JavaScript) to the browser, which it does using the paperboy module.

The integrated debugging in iisnode refactors the system in several ways shown on the picture below:

image

The key change is that both the browser instance running the debugger and the one running the application communicate with the backend using HTTP and HTTP long polling over a single port number. Websocket communication has been replaced with HTTP long polling. One port number and vanilla HTTP enable more robust deployments to a broader variety of hosting environments, and make it more resilient to a broader variety of proxies between the client and the server than is otherwise possible with websockets.

When iisnode receives a new HTTP requests, it routes the request to one of three destinations. HTTP long polling requests that relay the V8 debugging protocol are routed to the node-inspector. The requests for static content of the node-inspector UI (HTML, images, client side JavaScript) are re-routed back to IIS which uses the efficient, native static file handler to serve them back to the client (including server side output caching and compression). Finally, application requests are routed to the debugged application. Communication between iisnode and either the debugger or the debugee (both of which are node.js applications) uses HTTP over named pipes for increased efficiency compared to HTTP over TCP. In case of the debugger it is actually HTTP long polling over named pipes.

Closing words

With iisnode you can now easily debug node.js applications deployed to IIS from a variety of client platforms, including non-Windows. The design has been optimized to allow debugging after deployment to a broad variety of Windows-based shared hosting environments. The choice of protocols was optimized for robustness when communicating across firewalls and proxies.

This new feature has just shipped and I do expect issues will be found. I would appreciate your help in making iisnode better: please file any issues you encounter at https://github.com/tjanczuk/iisnode/issues.

Enjoy!

8 comments:

  1. Genius!

    This item was issue? Where i find the iisnode roadmap?

    ReplyDelete
  2. Hi,

    This is really amazing!

    How do you use the debugger with routes defined with the Express framework? I wondered if this could be triggered with a query string but I can't get anything to work. Is this possible?

    Thanks, Simon

    ReplyDelete
    Replies
    1. You can use the integrated node-inspector to debug an Express application. If you are using Express, you likely already have URL rewriting rules in your web.config (the section). To enable node-inspector integration, you must instruct the URL rewrite module to not rewrite URLs targeting the debugger. This can be done by adding one extra URL rewrite rule before all other rules in the section:

      <rule name="inspector" patternSyntax="ECMAScript" stopProcessing="true">
      <match url="^app.js" />
      </rule>

      (replace the "app.js" with the file name of your main entry point to the application).

      Delete
    2. Spot on... thanks so much!

      Simon

      Delete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Hello Tom,

    Can you help me to know a thing. I have seen your helloworld sample in iisnode on github. I am concerned about the meaning hello.js.debug folder. is this made because of debug hello.js in project.

    Thanks

    ReplyDelete
  5. One more question. As your current build on IISnode github. how I can run this code with node-inspector (hello.js)

    var express = require('express');

    var app = express.createServer();

    app.get('/', function(req, res) {
    res.send('test');
    });

    app.listen(process.env.PORT || 8080);

    ReplyDelete
  6. The *.debug folder is created to hold supporting files for debugging (a subset of node-inspector). Some of these files are served by IIS using a static file handler, hence they need to be physically on disk.

    In order to debug a node.js application through iisnode, you must ensure you use the appropriate URL to access the debugger and that the URL rewriting rules (if you use URL rewriting) do not modify that URL. See http://stackoverflow.com/a/10884756/174786 for more information.

    ReplyDelete

My Photo
My name is Tomasz Janczuk. I am currently working on my own venture - Mobile Chapters (http://mobilechapters.com). Formerly at Microsoft (12 years), focusing on node.js, JavaScript, Windows Azure, and .NET Framework.