The blog of dlaa.me

Posts tagged "Node.js"

"That's a funny looking warthog", a post about mocking Grunt [gruntMock is a simple mock for testing Grunt.js multi-tasks]

While writing the grunt-check-pages task for Grunt.js, I wanted a way to test the complete lifecycle: to load the task in a test context, run it against various inputs, and validate the output. It didn't seem practical to call into Grunt itself, so I looked around for a mock implementation of Grunt. There were plenty of mocks for use with Grunt, but I didn't find anything that mocked the API itself. So I wrote a very simple one and used it for testing.

That worked well, so I wanted to formalize my gruntMock implementation and post it as an npm package for others to use. Along the way, I added a bunch of additional API support and pulled in domain-based exception handling for a clean, self-contained implementation. As I hoped, updating grunt-check-pages made its tests simpler and more consistent.

Although gruntMock doesn't implement the complete Grunt API, it implements enough of it that I expect most tasks to be able to use it pretty easily. If not, please let me know what's missing! :)

 

For more context, here's part of the introductory section of README.md:

gruntMock is simple mock object that simulates the Grunt task runner for multi-tasks and can be easily integrated into a unit testing environment such as Nodeunit. gruntMock invokes tasks the same way Grunt does and exposes (almost) the same set of APIs. After providing input to a task, gruntMock runs and captures its output so tests can verify expected behavior. Task success and failure are unified, so it's easy to write positive and negative tests.

Here's what gruntMock looks like in a simple scenario under Nodeunit:

var gruntMock = require('gruntmock');
var example = require('./example-task.js');

exports.exampleTest = {

  pass: function(test) {
    test.expect(4);
    var mock = gruntMock.create({
      target: 'pass',
      files: [
        { src: ['unused.txt'] }
      ],
      options: { str: 'string', num: 1 }
    });
    mock.invoke(example, function(err) {
      test.ok(!err);
      test.equal(mock.logOk.length, 1);
      test.equal(mock.logOk[0], 'pass');
      test.equal(mock.logError.length, 0);
      test.done();
    });
  },

  fail: function(test) {
    test.expect(2);
    var mock = gruntMock.create({ target: 'fail' });
    mock.invoke(example, function(err) {
      test.ok(err);
      test.equal(err.message, 'fail');
      test.done();
    });
  }
};

 

For a more in-depth example, have a look at the use of gruntMock by grunt-check-pages. That shows off integration with other mocks (specifically nock, a nice HTTP server mock) as well as the testOutput helper function that's used to validate each test case's output without duplicating code. It also demonstrates how gruntMock's unified handling of success and failure allows for clean, consistent testing of input validation, happy path, and failure scenarios.

To learn more - or experiment with gruntMock - visit gruntMock on npm or gruntMock on GitHub.

Happy mocking!

Say goodbye to dead links and inconsistent formatting [grunt-check-pages is a simple Grunt task to check various aspects of a web page for correctness]

As part of converting my blog to a custom Node.js app, I wrote a set of tests to validate its routes, structure, content, and behavior (using mocha/grunt-mocha-test). Most of these tests are specific to my blog, but some are broadly applicable and I wanted to make them available to anyone who was interested. So I created a Grunt plugin and published it to npm:

grunt-check-pages

An important aspect of creating web sites is to validate the structure and content of their pages. The checkPages task provides an easy way to integrate this testing into your normal Grunt workflow.

By providing a list of pages to scan, the task can:

 

Link validation is fairly uncontroversial: you want to ensure each hyperlink on a page points to valid content. grunt-check-pages supports the standard HTML link types (ex: <a href="..."/>, <img src="..."/>) and makes an HTTP HEAD request to each link to make sure it's valid. (Because some web servers misbehave, the task also tries a GET request before reporting a link broken.) There are options to limit checking to same-domain links, to disallow links that redirect, and to provide a set of known-broken links to ignore. (FYI: Links in draft elements (ex: picture) are not supported for now.)

XHTML compliance might be a little controversial. I'm not here to persuade you to love XHTML - but I do have some experience parsing HTML and can reasonably make a few claims:

  • HTML syntax errors are tricky for browsers to interpret and (historically) no two work the same way
  • Parsing ambiguity leads to rendering issues which create browser-specific quirks and surprises
  • HTML5 is more prescriptive about invalid syntax, but nothing beats a well-formed document
  • Being able to confidently parse web pages with simple tools is pleasant and quite handy
  • Putting a close '/' on your img and br tags is a small price to pay for peace of mind :)

Accordingly, grunt-check-pages will (optionally) parse each page as XML and report the issues it finds.

 

grunt.initConfig({
  checkPages: {
    development: {
      options: {
        pageUrls: [
          'http://localhost:8080/',
          'http://localhost:8080/blog',
          'http://localhost:8080/about.html'
        ],
        checkLinks: true,
        onlySameDomainLinks: true,
        disallowRedirect: false,
        linksToIgnore: [
          'http://localhost:8080/broken.html'
        ],
        checkXhtml: true
      }
    },
    production: {
      options: {
        pageUrls: [
          'http://example.com/',
          'http://example.com/blog',
          'http://example.com/about.html'
        ],
        checkLinks: true,
        checkXhtml: true
      }
    }
  }
});

Something I find useful (and outline above) is to define separate configurations for development and production. My development configuration limits itself to links within the blog and ignores some that don't work when I'm self-hosting. My production configuration tests everything across a broader set of pages. This lets me iterate quickly during development while validating the live deployment more thoroughly.

If you'd like to incorporate grunt-check-pages into your workflow, you can get it via grunt-check-pages on npm or grunt-check-pages on GitHub. And if you have any feedback, please let me know!

 

Footnote: grunt-check-pages is not a site crawler; it looks at exactly the set of pages you ask it to. If you're looking for a crawler, you may be interested in something like grunt-link-checker (though I haven't used it myself).

Just because I'm paranoid doesn't mean they're not out to get me [Open-sourcing PassWeb: A simple, secure, cloud-based password manager]

I've used a password manager for many years because I feel that's the best way to maintain different (strong!) passwords for every account. I chose Password Safe when it was one of the only options and stuck with it until a year or so ago. Its one limitation was becoming more and more of an issue: it only runs on Windows and only on a PC. I'm increasingly using Windows on other devices (ex: phone or tablet) or running other operating systems (ex: iOS or Linux), and was unable to access my passwords more and more frequently.

To address that problem, I decided to switch to a cloud-based password manager for access from any platform. Surveying the landscape, there appeared to be many good options (some free, some paid), but I had a fundamental concern about trusting important personal data to someone else. Recent, high-profile hacks of large corporations suggest that some companies don't try all that hard to protect their customers' data. Unfortunately, the incentives aren't there to begin with, and the consequences (to the company) of a breach are fairly small.

Instead, I thought it would be interesting to write my own cloud-based password manager - because at least that way I'd know the author had my best interests at heart. :) On the flip side, I introduce the risk of my own mistake or bug compromising the system. But all software has bugs - so "Better the evil you know (or can manage) than the evil you don't". Good news is that I've taken steps to try to make things more secure; bad news is that it only takes one bug to throw everything out the window...

Hence the disclaimer:

I've tried to ensure PassWeb is safe and secure for normal use in low-risk environments, but do not trust me. Before using PassWeb, you should evaluate it against your unique needs, priorities, threats, and comfort level. If you find a problem or a weakness, please let me know so I can address it - but ultimately you use PassWeb as-is and at your own risk.

With that in mind, I'm open-sourcing PassWeb's code for others to use, play around with, or find bugs in. In addition to being a good way of sharing knowledge and helping others, this will satisfy the requests for code that I've already gotten. :)

Some highlights:

  • PassWeb's client is built with HTML, CSS, and JavaScript and is an offline-enabled single-page application.
  • It runs on all recent browsers (I've tried) and is mobile-friendly so it works well on phones, too.
  • Entries have a title, the name/password for an account, a link to the login page, and a notes section for additional details (like answers to security questions).
  • PassWeb can generate strong, random passwords; use them as-is, tweak them, or ignore them and type your own.
  • The small server component runs on ASP.NET or Node.js (I provide both implementations and a set of unit tests).
  • Data is encrypted via AES/CBC and only ever decrypted on the client (the server never sees the user name or password).
  • The code is small, with few dependencies, and should be easy to audit.

For details, instructions, and the code, visit the GitHub repository: https://github.com/DavidAnson/PassWeb

If you find PassWeb interesting or useful, that's great! If you have any thoughts or suggestions, please tell me. If you find a bug, let me know and I'll try to fix it.

Avoiding unnecessary dependencies by writing a few lines of code [A simple URL rewrite implementation for Node.js apps under iisnode removes the need for the URL Rewrite module]

As the administrator of multiple computers, I want the machines I run to be as stable and secure as possible. (Duh!) One way I go about that is by not installing anything that isn't absolutely necessary. So whenever I see a dependency that's providing only a tiny bit of functionality, I try to get rid of it. Such was the case with the URL Rewrite module for IIS when I migrated my blog to Node.js.

Aside: I'm not hating: URL Rewrite does some cool stuff, lots of people love it, and it's recommended by Microsoft. However, my sites don't use it, so it represented a new dependency and was therefore something I wanted to avoid. :)

The role URL Rewrite plays in the Node.js scenario is minimal - it points all requests to app.js, passes along the original URL for the Node app to handle, and that's about it. Tomasz Janczuk outlines the recommended configuration in his post Using URL rewriting with node.js applications hosted in IIS using iisnode.

 

I figured I could do the same thing with a few lines of code by defining a simple IHttpModule implementation in App_Code. So I did! :)

The class I wrote is named IisNodeUrlRewriter and using it is easy. Starting from a working Node.js application on iisnode (refer to Hosting node.js applications in IIS on Windows for guidance), all you need to do is:

  1. Put a copy of IisNodeUrlRewriter.cs in the App_Code folder
  2. Update the site's web.config to load the new module
  3. Profit!

Here's what the relevant part of web.config looks like:

<configuration>
  <system.webServer>
    <modules>
      <add name="IisNodeUrlRewriter" type="IisNodeUrlRewriter"/>
    </modules>
  </system.webServer>
</configuration>

 

Once enabled and running, IisNodeUrlRewriter rewrites all incoming requests to /app.js except for:

  • Requests at or under /iisnode, iisnode's default log directory
  • Requests at or under /app.js/debug, iisnode's node-inspector entry-point
  • Requests at or under /app.js.debug, the directory iisnode creates to support node-inspector

If you prefer the name server.js, you want to block one of the above paths in production, or you're running Node as part of a larger ASP.NET site (as I do), tweak the code to fit your scenario.

 

I modified the standard Node.js "hello world" sample to prove everything works and show the original request URL getting passed through:

require("http").createServer(function (req, res) {
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end("Requested URL: " + req.url);
}).listen(process.env.PORT, "127.0.0.1");
console.log("Server running...");

Sample output:

http://localhost/app.js
Requested URL: /app.js
http://localhost/
Requested URL: /
http://localhost/any/path?query=params
Requested URL: /any/path?query=params

I also verified that IIS's default logging of requests remains correct (though you can't tell that from the output above).

Note: I've found that accessing the debug path through IisNodeUrlRewriter can be somewhat finicky and take a few tries to load successfully. I'm not sure why, but because I don't use that functionality, I haven't spent much time investigating.

 

The implementation of IisNodeUrlRewriter is straightforward and boils down to two calls to HttpContext.RewritePath that leverage ASP.NET's default execution pipeline. One handy thing is the use of HttpContext.Items to save/restore the original URI. One obscure thing is the use of a single regular expression to match all three (actually six!) paths above. (If you're not comfortable with regular expressions or prefer to be more explicit, the relevant check can easily be turned into a set of string comparisons.)

The implementation is included below in its entirety; it ends up being more comments than code! I've also created a GitHub Gist for IisNodeUrlRewriter in case anyone wants to iterate on it or make improvements. (Some ideas: auto-detect the logging/debug paths by parsing web.config/iisnode.yml, use server.js instead of app.js when present, support redirects for only parts of the site, etc.)

 

// Copyright (c) 2014 by David Anson, http://dlaa.me/
// Released under the MIT license, http://opensource.org/licenses/MIT

using System;
using System.Text.RegularExpressions;
using System.Web;

/// <summary>
/// An IHttpModule that rewrites all URLs to /app.js (making the original URL available to the Node.js application).
/// </summary>
/// <remarks>
/// For use with Node.js applications running under iisnode on IIS as an alternative to installing the URL Rewrite module.
/// </remarks>
public class IisNodeUrlRewriter : IHttpModule
{
    /// <summary>
    /// Unique lookup key for the HttpContext.Items dictionary.
    /// </summary>
    private const string ItemsKey_PathAndQuery = "__IisNodeUrlRewriter_PathAndQuery__";

    /// <summary>
    /// Regex matching paths that should not be rewritten.
    /// </summary>
    /// <remarks>
    /// Specifically: /iisnode(/...) /app.js/debug(/...) and /app.js.debug(/...)
    /// </remarks>
    private Regex _noRewritePaths = new Regex(@"^/(iisnode|app\.js[/\.]debug)(/.*)?$", RegexOptions.IgnoreCase);

    /// <summary>
    /// Initializes a module and prepares it to handle requests.
    /// </summary>
    /// <param name="context">An HttpApplication that provides access to the methods, properties, and events common to all application objects within an ASP.NET application.</param>
    public void Init(HttpApplication context)
    {
        context.BeginRequest += HandleBeginRequest;
        context.PreRequestHandlerExecute += HandlePreRequestHandlerExecute;
    }

    /// <summary>
    /// Occurs as the first event in the HTTP pipeline chain of execution when ASP.NET responds to a request.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">An System.EventArgs that contains no event data.</param>
    private void HandleBeginRequest(object sender, EventArgs e)
    {
        // Get context
        var application = (HttpApplication)sender;
        var context = application.Context;

        // Rewrite all paths *except* those to the default log directory or those used for debugging
        if (!_noRewritePaths.IsMatch(context.Request.Path))
        {
            // Save original path
            context.Items[ItemsKey_PathAndQuery] = context.Request.Url.PathAndQuery;

            // Rewrite path
            context.RewritePath("/app.js");
        }
    }

    /// <summary>
    /// Occurs just before ASP.NET starts executing an event handler (for example, a page or an XML Web service).
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">An System.EventArgs that contains no event data.</param>
    private void HandlePreRequestHandlerExecute(object sender, EventArgs e)
    {
        // Get context
        var application = (HttpApplication)sender;
        var context = application.Context;

        // Restore original path (if present)
        var originalPath = context.Items[ItemsKey_PathAndQuery] as string;
        if (null != originalPath)
        {
            context.RewritePath(originalPath);
        }
    }

    /// <summary>
    /// Disposes of the resources (other than memory) used by the module that implements IHttpModule.
    /// </summary>
    public void Dispose()
    {
        // Nothing to do here; implementation of this method is required by IHttpModule
    }
}

Welcome to the new blog, same as the old blog. But different! [Hosting my blog at a new location on a new framework]

I've had blog since 2006. It's called Delay's Blog and - for a time - was among the top most visited blogs on MSDN. MSDN hosts its blogs on the Telligent platform and has a team of people whose job it is to keep things running. This is a nice perk and that team makes it easy for Microsoft employees to reach a wide audience. I had a good run and I appreciate all their efforts!

However, I'm something of a control freak and tinkerer and I've always thought it would be nice to own the whole content pipeline. So I decided a number of months ago to migrate this blog to my own site instead. Of course, there are a wealth of good blogging platforms I could have chosen, and lots that are based on the ASP.NET stack I use for the rest of my site.

But I didn't choose any of them - instead, I've written my own blogging platform based on the Node.js stack. While I'll be the first to acknowledge there's an element of NIH going on [ :) ], there were other considerations:

  • Node.js presents a good learning opportunity for a .NET guy like myself
  • My current job is all about HTML/CSS/JavaScript, so Node.js is a great fit
  • The Node.js community is very active and NPM has a wealth of great packages
  • Though I like typing posts in HTML, I want to experiment with Markdown
  • By writing my own blog, I have complete control (evil laugh...)

Development happened in small bits and pieces over many weeks and bus rides; the result is the blog you're reading now.

For the curious, here's what I used to build the site:

And here are a few of the features I implemented:

  • Post content is HTML or Markdown (original posts were migrated directly)
  • Support for both posts (in the timeline) and pages (separate, linkable content)
  • Responsive design scales/scrolls wide content and moves the sidebar when narrow
  • Database-less implementation makes content easy to deploy with Git
  • Short, deterministic, human-readable URLs for posts and pages
  • Pre-blogging support via automatic "go live" times for posts
  • Automatic server-side syntax highlighting for code samples
  • Simple, quick, semi-relevance-based search across all posts
  • Tags, archives, paged content, RSS, and other standard blog stuff

For convenience, I've migrated all the existing content from MSDN so I can reference it in one place (here!). For continuity, I've left the posts on MSDN, with comments disabled and a pointer to this site for new content.

As expected, developing my own blogging platform on a new framework was a great learning experience; I had a good time doing it and am happy to finally have complete control over my (blogging) destiny.

I even queued up new ideas for blog posts along the way! :)