Monday, August 29, 2011

Using URL rewriting with node.js applications hosted in IIS using iisnode

In my last post I introduced the iisnode project which allows hosting node.js applications in IIS on Windows. In this article I discuss using URL rewriting with node.js apps hosted in IIS, functionality necessary in all but the most trivial IIS hosted node.js applications.

The problem

Consider the hello world sample code, saved in the hello.js file in IIS virtual directory:

var http = require('http');

http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello, world! [helloworld sample]');
}).listen(process.env.PORT);

along with the following web.config that registers the iisnode module as a handler of the hello.js file, therefore indicating it is a node.js application:

<configuration>
<system.webServer>
<handlers>
<add name="iisnode" path="hello.js" verb="*" modules="iisnode" />
</handlers>
</system.webServer>
</configuration>

When both hello.js and web.config above are saved in the ‘node’ virtual directory in IIS, one can navigate to the node.js application using the following URL:

http://localhost/node/hello.js

As expected, IIS will realize the hello.js file maps to the iisnode handler and invoke it, and as expected a few million CPU cycles later a ‘Hello, world’ is sent back to the client.

However, when a subordinate URL path is requested, IIS returns an error, e.g:

image

The reason for this is that IIS does not understand that paths subordinate to the ‘hello.js’ component of the path should all be handled by the hello.js application. This is unacceptable for all but the most simplistic node.js applications, which typically own the entire URL space. Fortunately, it is easily remedied with the URL rewriting module.

URL rewriting module to the rescue

Fixing this problem requires configuring the URL rewriting module to indicate that the section of the request path that is subordinate to the hello.js component should be handled by the handler for the hello.js component itself. From IIS perspective the request processing occurs as if the request was made for http://localhost/node/helloworld/hello.js, but the original URL path is preserved and available for the handler to act on. URL rewriting is specified in the web.config file as follows (lines 17-24):

   1: <configuration>
   2:   <system.webServer>
   3:     <!-- indicates that the hello.js file is a node.js application 
   4:     to be handled by the iisnode module -->
   5:     <handlers>
   6:       <add name="iisnode" path="hello.js" verb="*" modules="iisnode" />
   7:     </handlers>
   8:     <!-- use URL rewriting to redirect the entire branch of the URL namespace
   9:     to hello.js node.js application; for example, the following URLs will 
  10:     all be handled by hello.js:
  11:     
  12:         http://localhost/node/urlrewrite/hello
  13:         http://localhost/node/urlrewrite/hello/foo
  14:         http://localhost/node/urlrewrite/hello/foo/bar/baz?param=bat
  15:         
  16:     -->    
  17:     <rewrite>
  18:       <rules>
  19:         <rule name="hello">
  20:           <match url="hello/*" />
  21:           <action type="Rewrite" url="hello.js" />
  22:         </rule>
  23:       </rules>
  24:     </rewrite>
  25:   </system.webServer>
  26: </configuration>

The configuration above will not only cause all URLs subordinate to hello.js to handled by hello.js node application; it also allows the hello.js file name to be completely removed from the URL path. With the configuration above the node.js service can now be accessed using any of the following URLs:

http://localhost/node/hello/foo/bar/baz
http://localhost/node/hello/1/2/3
http://localhost/node/hello/f/b/b?param=bat
...

What request URL does the node.js application see?

The request URL passed by IIS to the node.js application is the original URL from before re-writing. For example:

image

Where do I get iisnode again?

Everything you need to get started is at https://github.com/tjanczuk/iisnode. Make sure to check out the urlrewrite sample. Feedback welcome!

19 comments:

  1. This is pretty cool. Since NodeJS is living in IIS, do we get Windows Authentication for free?

    ReplyDelete
  2. Yes, you will get windows authentication as well as all the other IIS modules for free.

    ReplyDelete
  3. This is awesome. Thanks for the information. I ended up having to search for the url rewrite module for IIS on Windows 7, download and install it. Good bye to using ubuntu and a vm :)

    ReplyDelete
  4. I've got a question relating to URL Rewrite.

    Right now my nodejs program handles URL in the form:
    http://localhost/index

    Where "/index" is what my .js program sees as the "pathname", using the built-in url.parse() function. All functions are called from a single .js file.

    How would I set up URL Rewrite to simulate this? I would like my .js file to handle everything coming in on this machine. I don't want it to receive "/node/urlrewrite/etc/pararm

    I'm sure this is an easy URL Rewrite exercise, but I'm new to it.

    Thanks!

    ReplyDelete
  5. I have a question? Can you provide an iisnode JavaScript code example to get the windows user name from an authenticated user - assuming I have IIS set for windows authentication only.

    thank you

    ReplyDelete
  6. There is currently (v0.1.9) no way to extract the name of the Windows authenticated user from a node.js application running in iisnode. I've added https://github.com/tjanczuk/iisnode/issues/87 to address this issue.

    ReplyDelete
  7. Tomasz, tell me please how context collected by modules through the pipeline is going to be passed to iisnode?
    I see now huge problem with using Authentication and Authorization modules as well as with other which write something into context...

    ReplyDelete
  8. Hi Tomasz,

    I have an existing ASP.NET MVC 3 project and I want to utilise Socket.io and node by using iisnode.

    The problem I have is that I use Forms Authentication on the ASP.NET project, so when someone connects to the node server, I need to find a way of determining their authentication state in the MVC project, and act accordingly.

    Is there any way to do this?

    Many thanks

    ReplyDelete
    Replies
    1. If you are looking for push solution for a pre-exising MVC app, you should check out https://github.com/SignalR/SignalR/. It will likely integrate better with your app than building a cross-process solution.

      Delete
    2. Hi Tomasz, thanks for your advice. I have tested SignalR thoroughly but it has a couple of fatal flaws which they seem to be unwilling to address.

      The first is the lack of flash socket support (and for now, the lack of websocket support too until IIS 8 is released). This means that no browser can create proper bidirectional realtime sockets which means any communication method used is inferior.

      Socket.io on the other hand supports both, meaning that in my experience, 95% of browsers can use a true socket connection.

      They also have various issues with older browsers such as loading symbols and other issues, I don't understand why they tried to reinvent the wheel that socket.io had already implemented so much better.

      Given that for the above reasons I pretty much necessarily have to use Socket.io, how would you recommend I achieve communication, such as a single sign on, between node and ASP.NET?

      Many thanks

      Delete
    3. I am afraid using socket.io on top of IIS/iisnode will not enable you to use flash sockets or web sockets. The IIS/iisnode communication stack pretty much only supports HTTP transport, which restricts socket.io usage to xhr-polling transport. WebSocket support may be added to iisnode in the future once Windows 8 enables web socket support in HTTP.SYS and IIS.

      If you still intend to use socket.io with HTTP long polling (xhr-polling), you should be able to use Forms Authentication to authenticate calls to both the ASP.NET and node.js parts of your application, since the Forms Authentication is an IIS module that acts on all HTTP requests before they are handed over to the actual application (ASP.NET MVC or node.js in your case).

      Delete
    4. Thanks very much Tomasz, I wasn't aware of either the limitation in Socket.io on iisnode or that the requests to Socket.io would go through Forms Authentication.

      Given these limitations I will no doubt end up either running Socket.io outside of IIS to enable full socket functionality, or stick to SignalR - despite it's current flaws.

      If I was to run Socket.io outside of IIS, am I correct in assuming that I would need to use some kind of external session handler like a redis database to create a single sign on between ASP.NET and Socket.io? In your experience does the limitation in transfer protocols on SignalR adversely affect the scalability?

      Kind regards

      Delete
    5. @Wilkinsonj the fatal flaws you speak of aren't fatal for the 90% of cases out there. The various loading bar issues you filed are being worked on and fixed as I type here :). If you have some performance numbers that you would like to share with the SignalR team on how the other transports are lacking then I'd be glad to take a look at it.

      Delete
    6. Hi David, Good to hear.

      For my specific use case, the lag caused by xhr-polling is actually a stopper for the project.

      I'm glad to hear you're working on the other issues, although I still think you're duplicating a lot of the work socket.io already went through, perhaps there could have been a way to leverage their existing client?

      In terms of benchmarks, there are numerous benchmarks out there for socket.io showing the lag associated with xhr-polling. I guess the benefits I am looking for are probably the same reasons why you plan on supporting Websockets on IIS 8 when this is available.

      The main problem with this is that there is a high percentage of users who don't have browsers which support Websockets, even when you roll this out. With socket.io, there is a flash fallback which works suitably (and is an open source, client-side-only plug-in - appearing as a Websocket to the server), but I don't see even a plan to cater for this scenario in SignalR. I absolutely love SignalR, in terms of its architecture and the fact it allows me to develop in one single environment and leverage the existing authentication model, but I wish the aim was to satisfy more than just 90% of the use cases. I may very well end up creating a pull request and adding this feature myself, but it's impossible for me to test without having IIS 8. To be brutally honest I haven't yet contributed to any open source projects so it's a bit unfamiliar to me. I certainly feel like I could contribute a lot given the private projects I have led and this project may be as good a place to start as any other.

      Delete
  9. If you want to share state between ASP.NET app running in IIS and self-hosted node.js app you will indeed need maintain out of process state; I would probably look for a technology that is equally easily integrated into node.js as it is into ASP.NET. I am not sure how much there is in terms of Redis client for .NET. Perhaps MS SQL would actually be a better choice given you can now access that from both .NET and node. On the other hand, I would also look at some simple IPC mechanism like named pipes that allows you to exchange that state in real time between IIS and node. That, however, would require you to reconcile the process management issues between IIS and self-hosted node, since ASP.NET and node.js would now have different process lifetimes.

    All in all, I would first look at SignalR really, really hard before deciding to split your architecture that way. I am not sure whether the SignalR shortcomings you are referring to are worth the extra effort and cost to set up and maintain the more complex, hybrid solution.

    ReplyDelete
  10. Hi Tomasz. Thanks for your articles on iisnode, they've been a great help to get up and running. I am however running into a problem where I cannot seem to get URL Rewrite to work for both node and static files. I tried the web.config in your related post but it does not seem to work. I've tried a couple variations and have wound up with this (http://pastebin.com/Gqp0Z3FZ) which works for either static files or node, but not both (I selectively comment out the StaticContent or DynamicContent rule and their conditions). I noticed that you use {{TOKEN}} for your server variables, whereas I need to use {TOKEN} for mine to work at all. Is there a chance your articles were written around v1.0 of the URL Rewrite Module or something? Any help would be much appreciated. Thanks!

    ReplyDelete
    Replies
    1. After fiddling with it some more, I was able to get it to work with the updated config here: http://pastebin.com/hULgnCTg. Still don't quite understand why your example/boilerplate was not working though. Some additional information: I'm running Windows Server 2008 R2, IIS 7.5, node x64, and this app I've been having trouble with is using expressjs.

      Delete
  11. Can node.js application be configured to see relative path?
    For example when we get http://localhost/node/app.js/users/1 then node should see request for /users/1

    ReplyDelete
    Replies
    1. Would love to know the original author's recommendation to this question. Seems like it should be a IIS rewrite rule or nodeJS redirect or proxy on the node webserver.

      Delete

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.