Serving static files from iisnode with URL Rewrite - node.js

I am using a Rewrite rule in my web.config file for a node app running under issnode to point to my server.js file. myapp/* points to server.js.
<rule name="myapp" enabled="true">
<match url="myapp/*" />
<action type="Rewrite" url="server.js" />
</rule>
This has been working great www.mywebsite.com/myapp/ would load a run my app. What I wanted was to have a redirect from the root of the website so www.mywebsite.com/ would run my app. So I changed my web.config file
<rule name="myapp" enabled="true">
<match url="/*" />
<action type="Rewrite" url="server.js" />
</rule>
So this is running the server.js and serving up a my a static html file, the only problem is referencing any external files from my html file (css, js, images etc) Just get 500s for every request. I am using this to serve static files
var libpath = require('path');
var _path = "."; <-- This seems to be the problem
var uri = url.parse(req.url).pathname;
var filename = libpath.join(_path, uri);
fs.readFile(filename, "binary", function (err, file) {
if (err) {
res.writeHead(500, {
"Content-Type": "text/plain"
});
res.write(err + "\n");
res.end();
return;
}
var type = mime.lookup(filename);
res.writeHead(200, {
"Content-Type": type
});
res.write(file, "binary");
res.end();
});
break;
So my question is how to point to root of my node app / server to serve up static files.
Thanks
Jono

The best way to serve static content in iisnode is to configure the URL rewriting module such that the IIS static file handler handles requests for static content rather than node.js. Having IIS serve static content has a large performance benefit over serving these files using any node.js mechanisms due to kernel level optimizations around caching and just not having to break into JavaScript code.
For a boilerplate web.config configuration that achieves this see https://github.com/tjanczuk/iisnode/issues/160#issuecomment-5606547

I had some issues using the suggested rule config, so I made some changes:
<system.webServer>
<handlers>
<clear />
<add name="iisnode" path="/index.js" verb="*" modules="iisnode" />
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule,DefaultDocumentModule,DirectoryListingModule" resourceType="Either" requireAccess="Read" />
</handlers>
<rewrite>
<rules>
<rule name="static">
<action type="Rewrite" url="www{REQUEST_URI}" />
</rule>
<rule name="serve-static" stopProcessing="true">
<conditions logicalGrouping="MatchAny">
<add input="{REQUEST_FILENAME}" matchType="IsFile" />
<add input="{REQUEST_URI}" pattern="^/www/$" />
</conditions>
</rule>
<rule name="node">
<action type="Rewrite" url="index.js" />
</rule>
</rules>
</rewrite>
</system.webServer>
First rule prefixes all requests with www, my client directory.
Second rule stops processing if the file exists or if the root path is requested
Third rule rewrites anything else to the name of my backend Node script so that it can be picked up by the iisnode handler.
Note the iisnode handler path is set to /index.js which seems to eliminate conflicts with client files of the same name.

Related

Host NextJs to IIS

Good Day!
My colleague has a website node.js (next.js), his website works fine when we build and start thru console (npm run build and npm start).
We have hosted it in a Azure VM (Windows Server 2016 IIS, iisnode and urlrewrite installed), we created a pipeline and we are able to get the artifacts (".next" folder when we run the build) and deploy it to IIS however we still need a manual interaction to place the web.config. Below is the web.config
<!-- indicates that the hello.js file is a node.js application
to be handled by the iisnode module -->
<handlers>
<add name="iisnode" path="service-worker.js" verb="*" modules="iisnode" />
</handlers>
<!-- use URL rewriting to redirect the entire branch of the URL namespace
to hello.js node.js application; for example, the following URLs will
all be handled by hello.js:
http://localhost/node/express/myapp/foo
http://localhost/node/express/myapp/bar
-->
<rewrite>
<rules>
<rule name="AMS" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
But when we visit the website, it throws an error of 403 that need to supply the default page. (I'm lost here and not able to run his website thru IIS)
Note: His other website works fine (because it has a service-worker.js).
Anyone experience deploying the Next.JS to IIS? Thanks in Advance.
In the /public folder, create the following web.config to accept requests from /a/b/c and rewrite them to / where our NextJs code lives.
<?xml version="1.0"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="NextJs Routes" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Just doing this should allow you to reload a page on a route like /products, but NextJs will render /, ie, the index page, because that's what our rewrite rule told it to deliver.
So, we need to create a body Component that takes a NextRouter as a prop then compare the window's url to the router's url. If they don't match, we need to change our client side route with router.push().
I'm using TypeScript so my body.tsx is
import * as React from 'react';
import { NextRouter } from 'next/router';
export default class Body extends React.Component<{router : NextRouter}>
{
componentDidMount()
{
if (window.location.pathname == this.props.router.pathname) return;
this.props.router.push(global.window.location.pathname);
}
render = () => this.props.children;
}
Then in _app.tsx, we simply need to wrap the main Component in our Body Component.
import { useRouter } from 'next/router'
import Head from 'next/head';
import Body from '../src/components/elements/body';
function MyApp({ Component, pageProps }) {
const router = useRouter();
return (
<>
<Head>
<title>NextJs on IIS</title>
</Head>
<Body router={router}>
<Component {...pageProps} />
</Body>
</>
)
}
export default MyApp
Run npm run build, and copy the /out folder to your IIS server.

Run node.js & WebAPI on same server for same site with IIS

I'm looking to slowly convert a Node.js application over to ASP.NET WebAPI 2.0. I'm currently using IIS and will stick with IIS. So, I would like to host them on the same server but direct some URIs over to the new platform.
How would I do this in the web.config? The current web.config for node.js looks like so:
<configuration>
<system.webServer>
<handlers>
<!-- indicates that the app.js file is a node.js application
to be handled by the iisnode module -->
<add name="iisnode" path="beta/app.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<!-- Don't interfere with requests for node-inspector debugging -->
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^beta/app.js\/debug[\/]?" />
</rule>
<!-- First we consider whether the incoming URL matches a physical file in the /public folder -->
<rule name="StaticContent">
<action type="Rewrite" url="beta/public{REQUEST_URI}" />
</rule>
<!-- All other URLs are mapped to the Node.js application entry point -->
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True" />
</conditions>
<action type="Rewrite" url="beta/app.js" />
</rule>
</rules>
</rewrite>
<httpErrors errorMode="Detailed"/>
</system.webServer>
</configuration>
The file structure is:
- web.config (the one shown above)
-> node
- app.js
- ...
-> webapi
- web.config
- global.asax
- ...
I was thinking that I should be writing a new rule which lists the URIs to go to the WebAPI. But, I'm not quite sure how to do that. My guess is that I would add a condition for each URI with the input attribute. I was also thinking I should point to the ASP.NET WebAPI project but I am even more clueless how I should go about doing that since Node.js I'm just pointing at the app.js file.
OK, this is what I ended up doing. It was actually pretty straight forward. But when you are not familiar with IIS it can be daunting.
I put the original web.config in with the node directory. I think the iisnode handler interferes with WebAPI config if you don't. So, the new node.js web.config in the node directory would look like this:
<configuration>
<system.webServer>
<handlers>
<!-- indicates that the app.js file is a node.js application
to be handled by the iisnode module -->
<add name="iisnode" path="app.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^app.js\/debug[\/]?" />
</rule>
</rules>
</rewrite>
<httpErrors errorMode="Detailed"/>
</system.webServer>
</configuration>
For root web.config I made it point to static files directly, bypassing node.js. Which means I'm going to have to write some custom code to handle rewrites for gzipped files - I'll figure that out later. I also added the attribute stopProcessing to each rewrite rule. This was also messing up the code, as it wouldn't actually rewrite where I wanted it too, since the rewrite would be overwritten. Note that the accept versioning header hasn't actually been tested yet - I don't have any reason to believe it wouldn't work though. The last rewrite points all uris to the webapi app by default.
In the WebAPI project I had to route all my routes to webapi/api since it isn't in the root folder. After I migrate everything from node.js I will probably make the webapi directory the root folder for the project so it won't need the webapi in my routing anymore. But this is all hidden from the client.
So here's the actual code:
<configuration>
<system.webServer>
<rewrite>
<rules>
<!-- test item for webapi folder -->
<rule name="StaticContent2" stopProcessing="true" >
<conditions>
<add input="{REQUEST_URI}" pattern="^/def" />
</conditions>
<action type="Rewrite" url="webapi{REQUEST_URI}" />
</rule>
<!-- rewrite static items which exist on node -->
<rule name="Node Static" stopProcessing="true" >
<conditions>
<add input="{REQUEST_URI}" pattern=".*\.[A-Za-z2]{2,5}$" />
</conditions>
<action type="Rewrite" url="node/public{REQUEST_URI}" />
</rule>
<rule name="WebAPI Version 2" stopProcessing="true">
<conditions>
<add
input="{HEADER_ACCEPT}"
pattern="vnd.fieldops.v2"
ignoreCase="true"
/>
</conditions>
<action type="Rewrite" url="webapi{REQUEST_URI}" />
</rule>
<!-- rewrite to node for dynamic items -->
<rule name="Node Dynamic" stopProcessing="true" >
<conditions>
<add
input="{REQUEST_URI}"
pattern="^/api/(dealerservicereports|chat|dealers|dealerequipment|dealercloseout|publications|tokens|users|\?)"
ignoreCase="true"
/>
</conditions>
<action type="Rewrite" url="node/app.js" />
</rule>
<!-- rewrite everything else to webapi -->
<rule name="WebAPI Dynamic" stopProcessing="true" >
<action type="Rewrite" url="webapi{REQUEST_URI}" />
</rule>
</rules>
</rewrite>
<httpErrors errorMode="Detailed"/>
</system.webServer>
</configuration>

Azure Web App Rewrite rule not working properly because of Output caching

I have a Azure node web app. Web.config taken from here
I have a http -> https redirection rule in web.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="iisnode" path="server.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<rule name="Force HTTPS" stopProcessing="true">
<match url="(.*)"/>
<conditions>
<add input="{HTTPS}" pattern="^OFF$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}
{REQUEST_URI}" appendQueryString="false"
redirectType="Permanent" />
</rule>
<rule name="StaticContent">
<action type="Rewrite" url="public{REQUEST_URI}" />
</rule>
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile"
negate="True" />
</conditions>
<action type="Rewrite" url="server.js" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
After the server restarts http://site/index.html -> https://site/index.html works, but on quick subsequent request http://site/index.html just serves the cached output (client caching is disabled). Though, redirection starts working after short period of inactivity but problem re-occurs.
Disabling output cache as below solves the problem (Output cache is enabled by default and for solution disabling enableKernelCache is enough)
<system.webServer>
<caching enable="false" enableKernelCache="false" />
It seems like Output cache is sending the response before hitting the rewrite rules.
Has anyone encountered the same problem? Is this the optimal solution (or if any) or is there any performance setback with this solution (the link says output caching targets semi-dynamic content)?
Note** Couldn't reproduce locally
EDIT:
The following website is a sample setup where this problem occurs.
Project setup
wwwroot
-- web.config (above)
-- server.js
-- public
-- index.html
-- main.css
Server.js
var http = require('http');
function handleRequest(request, response){
response.end('It Works!! Path Hit: ' + request.url);
}
var server = http.createServer(handleRequest);
var port = process.env.PORT || "80";
server.listen(port, "0.0.0.0", 511, function(){
console.log("Server listening on: http://localhost:%s", port);
});
After David Ebbo comments below I used curl and to my wonder it worked meanwhile it was not working in the browser. So it got me thinking and after playing with some headers( I looked up what headers browser is sending) I can reproduce it in curl too. When making curl request with Accept-encoding: gzip curl also doesn't redirect. (It might be hard to reproduce this just making curl request with the header. Try the curl command when it fails in the browser)
After reading up here, it seems like compression for dynamic content is done everytime while not for the static content. And it seems true cause
http://test-redirect.azurewebsites.net (which is a dynamic endpoint) always redirects
But, doing this does not solve the problem
<urlCompression doStaticCompression="false" doDynamicCompression="true" />
Instead now curl without header can reproduce the problem

HTTPS and iisnode

I'm using node in combination with IIS by using iisnode.
I seems to me that things that I was previously doing in Node to configure the server can now be done directly in IIS.
Things like:
https configuration (and certificates)
http to https redirection
Does this mean I can get rid of the node code that did that and go just for the IIS method?
var fs = require('fs');
var https = require('https');
var options = {
key: fs.readFileSync('./ssl/xxxxxxx.private.pem'),
cert: fs.readFileSync('./ssl/xxxxxxx.public.pem'),
};
https.createServer(options, app).listen(443);
Your keys and pfx should never live on the file system. One slip up could serve your files to the internet and now everyone can get your key. Storing them in the windows cert store is best.
Yes. You should do all the ssl configuration on IIS and Windows.
This is what I have used on production.
On the application, you should simply write:
var app = express();
app.listen(process.env.port);
Then web.config for iisnode should look like this:
<configuration>
<system.webServer>
<handlers>
<add name="iisnode" path="app.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<rule name="HTTP to Prod HTTPS redirect" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" redirectType="Found" url="https://{HTTP_HOST}/{R:1}" />
</rule>
<!-- Don't interfere with requests for logs -->
<rule name="LogFile" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^[a-zA-Z0-9_\-]+\.js\.logs\/\d+\.txt$" />
</rule>
<!-- Don't interfere with requests for node-inspector debugging -->
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^app.js\/debug[\/]?" />
</rule>
<!-- First we consider whether the incoming URL matches a physical file in the /public folder -->
<rule name="StaticContent">
<action type="Rewrite" url="public{REQUEST_URI}" />
</rule>
<!-- All other URLs are mapped to the Node.js application entry point -->
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True" />
</conditions>
<action type="Rewrite" url="app.js" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

Sails looking in wrong directory

I'm new to sails.js and node.js, so the question might be trivial, but I couldn't find the answer. I have deployed my node.js app to a web site in IIS, so the app can be reached at http://example.com/myapp/. When browsing to http://myhost.com/myapp/app.js, I get http status 404 (Not found), because sail.jss is looking for URLs like http://myhost.com/images/logo.png, but this file is in fact located at http://myhost.com/myapp/.tmp/public/images/logo.png. This .tmp folder seems to be created on the fly by the framework.
Can someone shed some light on this?
[edit]
I have added rewrite rules in the web.config and it works much better. But it only works if I put the application at the root of my web site (acessing http://myhost.com/). If I put the application in a lower level (accessing through http://myhost.com/myApp), then the added rules do not seem to produce any effect.
Here is the web.config:
<handlers>
<add name="iisnode" path="app.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<rule name="StaticContent">
<action type="Rewrite" url="assets{REQUEST_URI}"/>
</rule>
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
</conditions>
<action type="Rewrite" url="app.js"/>
</rule>
</rules>
</rewrite>
The key is to allow Express to handle all of the routing. The best way to do that is to route all traffic to app.js via iisnode (from: https://nodestream.wordpress.com/2015/11/24/sails-js-configuration-for-iis/):
<configuration>
<system.webServer>
<!-- Tell IIS to use the iisnode module to run your
application -->
<handlers>
<add name="iisnode" path="app.js" verb="*" modules="iisnode" />
</handlers>
<!-- Add iisnode with the #nodeProcessCommand line if
you see the error: Make sure the node.exe executable
is available at the location specified in the
system.webServer/iisnode/#nodeProcessCommandLine element
of web.config. -->
<iisnode
nodeProcessCommandLine="%ProgramFiles%\nodejs\node.exe"
/>
<!-- Since behind the covers, Sails.js is just an express app
rewrite all urls to processed by iisnode via app.js. This
will sort out things like the routing to your public
resources (images, js, styles) and all configured rest
endpoints. -->
<rewrite>
<rules>
<rule name="root">
<match url=".*" />
<action type="Rewrite" url="app.js" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Well the .tmp folder is created by Grunt. You can reference the gruntfile and the task folder. The pipeline.js allows you to select files/folders for grunt to inject and spit out. You can easily change this to point to /images and /js folders.
tasks/pipline.js
module.exports.cssFilesToInject = cssFilesToInject.map(function(path) {
return '.tmp/public/' + path; // Change this
});
module.exports.jsFilesToInject = jsFilesToInject.map(function(path) {
return '.tmp/public/' + path; // Change this
});
Another solution I could think of, however I am not sure if IIS has it, is to do a rewrite rule. When a user goes to site.com/images, point them to .tmp/public/images. It is common to see that in Apache servers.

Resources