Implementing an ISAPI rewrite for IIS 6

Standard
Share

In an effort to clean up the URLs my WordPress-based site generated, I wanted to implement an ISAPI rewrite. You see, because I host my site on IIS 6, turning on WordPress’ Permalinks required an “/index.php/” in all of my URLs. I wanted a way to eliminate the “/index.php/” portion of the URL.  If I were hosting this site on an Apache server, it would be a no-brainer as Apache has the mod_rewrite module available, and would be able to serve URLs without the /index.php/ out of the box.  According to http://httpd.apache.org, the mod_rewrite module “uses a rule-based rewriting engine, based on a PCRE regular-expression parser, to rewrite requested URLs on the fly.”  It is this module that allows the intricate URLs created by WordPress to be presented in a friendly, cleaner manner – allowing http://jkshay.com/index.php/implementing-an-isapi-rewrite-for-iis to be accessed with a URL such as http://jkshay.com/implementing-an-isapi-rewrite-for-iis-6.

But what if I’m not running Apache?  IIS 7.5 includes rewriting abilities, but I’m running Windows Home Server, which implements IIS 6.  Sadly, IIS 6 comes with no URL rewriting technology, so we’re left to third parties to solve the issue.

I initially tried HeliconTech’s ISAPI Rewriter 3 lite. It worked great for my WordPress site, but it is not written for multi-site servers or virtual folders.  Implementing their lite version managed to break my Drupal site, my HPMediaStreamer site, my PHPMyAdmin virtual folder, my webmail virtual folder… you get the picture.  To be fair, Helicon’s website warned me that their lite product was not appropriate for multi-site servers, and that I needed their full-blown ISAPI rewriter for a server like mine.  However, I’m not willing to spend the $99 they want.

So my search continued, and I stumbled across Ionic’s ISAPI Rewrite Filter (IIRF).  After poring over the documentation, I was able to get pretty permalinks working – for the most part.

First, I changed my site’s permalinks structure to a custom /%postname%/ structure. Then I installed IIRF via it’s .msi installation routine and added the following to an iirf.ini file located in my site’s root:

RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/wp-.*
RewriteCond %{REQUEST_URI} !^/index.php/.*
RewriteRule ^(.*)$ /index.php/$1 [L]

Let me break this down – we start with

RewriteBase /

which strips away the domain name. Straight from the IIRF documentation: “RewriteBase tells IIRF to strip the provided URL base from the incoming request, before applying test patterns.” This makes it easy to copy an existing iirf.ini file to multiple sites without the need for change. For example, if I host http://www.jkshay.com and http://www.jkshay.info on the same server, and wanted the same IIRF rules (via identical Iirf.ini files) to apply to both, using the RewriteBase directive allows me to do that.  If I didn’t use the RewriteBase directive, I’d have to account for this in the regular expressions of my other directives.

The next line is

RewriteCond %{REQUEST_FILENAME} !-f

First let’s define the syntax for the RewriteCond directive.

The syntax for the RewriteCond directive is:
RewriteCond <test-string> <pattern>  [<modifier[,…]>]

In a nutshell (and taken directly from the IIRF documentation), “This directive applies a condition to the next following RewriteRule…” Basically, it puts a qualifier on the RewriteRule directive.

Second, let’s define our test string %{REQUEST_FILENAME}. It takes the form of a server variable. However, it is not a true IIS server variable, but what the author of IIRF calls a synthetic server variable. Again, straight from the IIRF docs: “REQUEST_FILENAME will evaluate to the physical path for the requested URL, which may or may not correspond to an existing file.”

Third, let’s define our pattern -f. Typically we’d use a regular expression as the pattern, but we’re using instead what the IIRF author calls a special condition variant. The -f special condition variant tests the status of the filesystem. From the IIRF documentation: (-f) “Treats the test-string as a pathname and tests if it exists and is a regular file (and not a symbolic link).” The ! preceding the special condition variant is the regular expression negator.

We’re not using any modifiers, so I’ll skip the description of that portion for now.

If we put them all together, we can see we’re telling IIRF to rewrite the incoming request only if the URL (%{REQUEST_FILENAME}) does not (!) exist as a regular file (-f).

The next line in our Iirf.ini file is:

RewriteCond %{REQUEST_FILENAME} !-d

We’ve already covered the RewriteCond syntax, and defined the %{REQUEST_FILENAME} synthetic server variable. The difference in this directive from the previous is the pattern !-d. According to the IIRF documentation, the -d special condition variant “treats the test-string as a pathname and tests if it exists, and is a directory.” Once again, we’re prepending the regular expression negator !.

Put it all together, and we’re telling IIRF to rewrite the incoming request only if the URL (%{REQUEST_FILENAME}) does not (!) exist as a directory (-d).

The fourth line in our Iirf.ini file is:

RewriteCond %{REQUEST_URI} !^/wp-.*

This directive is a bit different than the previous two, as we’re using a different test string and a true regular expression as the pattern.

The ${REQUEST_URI} test string is another synthetic server variable. According to the IIRF documentation, “REQUEST_URI evaluates to the original URI, including uri path and query string, but not including the scheme, host or port.”

The regular expression !^/wp-.* breaks down as follows:

  1. ! – our old friend, the regular expression negator
  2. ^ – defines the anchor position as the position prior to the first character. Basically, it says “the string must start with…”
  3. /wp- – the literal character string we’re trying to match
  4. .* – any series of characters, or none at all

In a nutshell, this regular expression matches anything that does not (!) begin with “/wp-” (/wp-) and is followed by anything or nothing (.*). Therefore, this directive applies the condition that the URI must not begin with “/wp-“.

The next directive is

RewriteCond %{REQUEST_URI} !^/index.php/.*

Much like the previous directive, this rewrite condition states that the requested uri (%{REQUEST_URI}) must not (!) begin with (^) the string “/index.php/” (/index.php/) followed by any sequence of characters or none at all (.*).

Note that by default, multiple consecutive RewriteCond directives are joined with a logical AND operator. Therefore, all consecutive RewriteCond directives must evaluate to TRUE in order for the rewrite rule to become effective. So the condition we’re setting on this rewrite rule is:

  1. The requested filename does not exist as a file on the filesystem AND
  2. The requested filename does not exist as a directory on the filesystem AND
  3. The requested URI does not begin with “/wp-” AND
  4. The requested URI does not begin with “/index.php/”

Now let’s take a look at our rewrite rule:

RewriteRule ^(.*)$ /index.php/$1 [L]

First, let’s look at the syntax for the RewriteRule directive:
RewriteRule <url-pattern> <replacement-string> [<modifiers>]

Next, breaking down the url-pattern ^(.*)$ introduces a new regular expression anchor. The $ basically matches to the first character after the last character in the string. Combined with the ^ (which signifies the start of the string) and (.*) (which signifies any sequence of characters, or no sequence at all), we’re basically grabbing ANY URL that matches the previously defined RewriteCond rewrite condition directives.

Third, the replacement-string /index.php/$1 introduces literal text as well as a back reference ($1). A back reference refers to the matched substring. Basically, if the requested URL matches all the previously defined conditions, we’re going to insert a “/index.php/” between the uri base and the requested uri. Requesting “http://www.jkshay.com/implementing-an-isapi-rewrite-for-iis-6” gets interpreted as a request for “http://www.jkshay.com/index.php/implementing-an-isapi-rewrite-for-iis-6“, which my site is happy to process.

And finally, we’re using the [L] modifier. From the IIRF documentation, [L] “tells IIRF to process no more patterns if the current one matches.” I’m pretty sure we could eliminate this modifier, as this is the last rule in the chain and no other rules exist that could modify the URL if we didn’t specify the modifier.

To recap, I’m using Ionic’s ISAPI Rewriting Filter to implement pretty permalinks in my WordPress site. It is open source and free (donationware, actually, although at the time of this article the donation link – http://cheeso.members.winisp.net/IirfDonate.aspx appears to be invalid), and allows configuration on a per website or per virtual directory basis. My rules check that the requested URI:

  1. does not exist as a file on the server
  2. does not exist as a directory on the server
  3. does not begin with “/wp-“
  4. does not begin with “/index.php/”

When a requested URL matches these conditions, it inserts “/index.php/” between the requested URI base and the remainder of the requested URI. We’re also stating that this RewriteRule is the last directive that should process any URL requests that match the conditions.

I’m sure I’ll come across a feature or two of WordPress that I haven’t considered that will not work with my existing rules. When that happens, I’ll update this post with those problems and new RewriteCond directives as they occur.

[edit]
It didn’t take me long to find an issue with my code. I like for my articles to be fully justified, and it became apparent rather quickly that style wasn’t being applied. I style my site through custom css (Appearance –> Edit CSS) provided by the Jetpack plugin.

#content {
	position: relative;
	text-align: justify;
}

I was confused, as the custom css file that my styles live in exists in a filepath that contains “wp-“. Viewing the source of my page revealed why I lost my custom-css applied styles. The URI requested was “http://jkshay.com/?custom-css=1&csblog=1&cscache=6&csrev=38”. You can see that the URI specifically does not include the “wp-” string. I resolved this issue by adding the following RewriteCond directive to my Iirf.ini file:

RewriteCond %{REQUEST_URI} !.*custom-css.*

What we’re doing here is simply adding the condition that the requested URI does not (!) include the text “custom-css”, anywhere in the string (the .* before and after the string).

My complete Iirf.ini file is as follows:

StatusInquiry On /iirfStatus
RewriteLog C:\temp\
RewriteLogLevel 1

RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/wp-.*
RewriteCond %{REQUEST_URI} !^/index.php/.*
RewriteCond %{REQUEST_URI} !.*custom-css.*
RewriteRule ^(.*)$ /index.php/$1 [L]

The first three lines are not necessary for pretty permalinks to work properly. However, they ARE suggested for diagnostic purposes, and aided me in getting this to work on my site.

15 thoughts on “Implementing an ISAPI rewrite for IIS 6

    • I’m not familiar with Discuz. Does it offer any kind of SEO-friendly URLs? I imagine it would work if the Discuz software supports pretty permalinks. Can you provide any information regarding the URL configuration options in Discuz?

        • Frank, I can do some testing this weekend. I’ll configure an ASP 4 site, throw WordPress on it, and see what happens!

          Out of curiosity, have you tried implementing the WordPress installation as a Virtual Directory?

          • Frank

            No, as of now we have not…

            So you know, the link I provided is obviously a sub folder of the site which we are going to make live so the solution will be implemented on the root which is currently accupointe.com

            Thank you very much for your support and help…hopefully we can find a solution!

    • Frank, I just installed .NET 4.0 on my server and changed the ASP.NET version on my site (through IIS, right-click site node, select “Properties” and choose “ASP.NET” tab) to version 4.0.30319.

      I’m not sure if this is equivalent to what you’re attempting to do (I’m not a web developer, and my web server knowledge is slim at best), but it seems like it did NOT break the URL redirection provided by IIRF.

  1. Igor

    Hi,

    I followed your instructions to the letter however it does not work. All i get is a 404 error i.e. The page cannot be found. This happens to all the pages regardless of whether it’s the home page or any other page.

    • Igor

      Sorry. I also noticed it doesn’t work if the home page is domain.com but does work if it is domain.com/index.php. I would prefer it working with domain.com.

      • Igor

        Hi,

        I did manage to get it to work. A minor tweak. Entire block of code below if someone ever runs into the same issue.

        RewriteBase /
        
        ## Rewrite the empty URL to the index.php homepage
        RewriteRule ^/($|\?)   /index.php   [L,QSA]
        ## Redirect index.php to the empty url
        RedirectRule ^/index.php$            /   [R=302]
        
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteCond %{REQUEST_URI} !^/wp-.*
        RewriteCond %{REQUEST_URI} !^/index.php/.*
        RewriteCond %{REQUEST_URI} !.*custom-css.*
        RewriteRule ^(.*)$ /index.php/$1 [NC,L]
        
        • Thanks for the information and updated code, Igor. I’ve since moved my server to a cloud-based linux server, but I’ve kept your comment code for others that may run into the same issue.

Leave a Reply

Your email address will not be published. Required fields are marked *