Ryan Kanno: The diary of an Enginerd in Hawaii

Everything you’ve ever thought, but never had the balls to say.

CakePHP and IIS and ISAPI Rewrite - Demystified!

Update: 11/16/2007

One of my commenters, James has provided a working production solution versus my “hacked” solution. See the comments and be sure to visit his site. :) Thank you James!


@ work, Stephen and I were tasked with fulfilling a requirement for the big boss, so we selected an up and coming Rails-like framework called CakePHP. Cake’s really fun and easy to use and like Ubuntu, “everything just works“… until you try hooking it up into IIS with ‘pretty URLs’. It took us a day of testing, hair pulling, more testing, more hair pulling, but finally - we’ve debunked the mystery.

First, let me start off by saying that we read all the wiki documentation, searched google groups, and though we found people doing similar things, we didn’t find any solution that satisifed our needs. We found partial solutions with poorly written wiki tutorials, so that’s why you’re reading a brand-new one.

The setup

Our setup consisted of the Cake framework (cake_1.1.6.3264.zip) running on Win2K w/ IIS5 and PHP5 running as an ISAPI Dll. We created a virtual directory named “tp” that’s currently mapped to the app/webroot directory in our Cake installation directory. From reading all the available tutorials, Cake’s functionality all boils down to passing index.php in the app/webroot directory the following:


?url=/controllerName/action/params

This, my friend, is how all the magic begins. So, from the web, I should always be able to reach the following URL:


http://domain.com/tp/index.php?url=/controllerName/action/params

The get-go

Here are the following results from our tests:

Test 1: Do nothing

Stephen and I both hypothesized that by doing nothing, we’d just manually echo out all linking URL’s, i.e. we wouldn’t use the $html->link helper function. This works, period. However, if we do nothing to the configuration, and we do employ the $html->link helper, $html->link(”/controllerName/action”) writes out the following URL:


http://domain.com/controllerName/action

First off, since we have a virtual directory named “tp”, this obviously won’t work. Second, if I define (’WEBROOT_DIR’, ‘tp’) within app/webroot/index.php and then use $html->link(’/controllerName/action’), I get the following URL instead:


http://domain.com/tp/controllerName/action

Closer, but this still doesn’t work because ISAPI Rewrite isn’t enabled. So… that leaves us wondering, how do we use $html->link without modifying the configuration? In any case, we revert the variable WEBROOT_DIR back to its original setting for our next test.

Test 2: Enable Cake Short URLs

With Cake Short URLs enabled, a user will be able to access particular actions via the following URL:


http://domain.com/tp/index.php/controllerName/action

To enable the short URLs, I followed the wiki tutorial. Basically, this involves uncommenting the BASE_URL environment variable and defining (’SERVER_IIS’, true) in core.php in the app/config directory. Now, when I use the $html->link helper, these configurations enabled inserting /tp/index.php/ into the url and everything works! For example, $html->link(’/controllerName/action’) now prints the following:


http://domain.com/tp/index.php/controllerName/action


(And it works too!)

This solves the problem but still doesn’t give us the ideal solution. After all, who wants that index.php within the URL? Not to mention, enabling this configuration setting made it impossible to access the following (original) URL:


http://domain.com/tp/index.php?url=/controllerName/action/params

Is this the expected behavior? As a side note, I can now access URLS as such:


http://domain.com/tp/index.php?/controllerName/action/params

Notice the ‘?’ without the ‘url=’. Is this expected behavior as well? In any case, on to enable ISAPI Rewrite.

Test 3: Enable ISAPI Rewrite

To enable ISAPI Rewrite, we know we had to disable Cake Short URLs by disabling the BASE_URL variable, but at the same time, we decided to leave the IIS_SERVER variable to true. Next, we decided to follow this tutorial on the Cake wiki. No offense to the author, but this entry is incomplete and leaves no explanation as to how the author came to those Rewrite rules. Also, the tutorial doesn’t explain how to enable the pretty URLs for a site running in a virtual directory.

So, we downloaded the ISAPI Rewrite Dll from http://www.isapirewrite.com/. Note, download the free version, but realize that there are limitations on what it can do. Enable the ISAPI Dll @ the Site level since you need the full version for granularity at the virtual directory level. The httpd.ini file that’s listed in the wiki only works if you’re running Cake from the IIS Site level and not from a virtual directory.

Our configuration

Here’s what we did to enable the ISAPI Rewrite from a virtual directory. Our httpd.ini file looks like the following:

[ISAPI_Rewrite] 

// This rule catches the Short URL settings
RewriteRule (.*?\\.php)(\\?[^/]*)?/([^/]*)/(.*) $1(?2$2&:\\?url=/$3/$4) 

//This rule performs the ISAPI Rewrite rules
RewriteRule ^/VIRTUAL_DIRECTORY_NAME_HERE/(.*)/ /VIRTUAL_DIRECTORY_NAME_HERE/index.php?url=/$1

Note, replace virtual_directory_name_here with the name of your virtual directory. As you can see, RewriteRule ^/VIRTUAL_DIRECTORY_NAME_HERE/(.*)/ /VIRTUAL_DIRECTORY_NAME_HERE/index.php?url=/$1 is taking the following URL:

/virtual_directory/controllerName/action/ and rewriting it to /virtual_directory/index.php?url=/controllerName/action.

This is exactly how we want it!

Wait, as you can see though, the RewriteRule matches URLs that have a slash at the end. We figured that the original wiki poster left the slash so that this rule wouldn’t match any of the files being served out of app/webroot such as the css/img files.

Wait… what implications does this have?

Well, this just means that when you use the $html->link helper, you’ll need to end all your actions with a “/”. For example, if you want to link to /controllerName/action, you must now type $html->link(”/controllerName/action/”). Now, this will match the second RewriteRule and everything will work!

However, there’s just one tiny piece we’re forgetting. If you did try this out on your site, you’ll find that the URLs generated from $html->link still don’t have the virtual directory name in it. You’ll see that the links generated will be http://www.domain.com/controllerName/action/. Well, from our experience in past tests, this means you have to define(’WEBROOT_DIR’, ‘virtual_directory_name’) in the app/webroot/index.php file and voila…

Everything now works!

  1. This is a great tutorial for those of us who have to use IIS but want to use the PHP Cake framework, you saved me a lot of heartache! I had been developing a site on Apache but just learned that it was going to be hosted on a Windows server. I thought it wasn’t going to be a problem until I tried configuring ISAPI_Rewrite and kept on getting 404 errors!

  2. which .htaccess file did you replace?
    A standard Cake install has:
    /httpdocs/.htaccess
    /httpdocs/app/.htaccess
    /httpdocs/app/webroot/.htaccess

  3. Rob, I think he left the .htaccess files alone, since the free Isapi_Rewrite thing doesn’t do per-directory .htaccess. Also, he says he modified httpd.ini, which, in the latest version of ISAPI_Rewrite, is httpd.conf in the installation folder.

  4. Here’s another setup which appears to work well if you want to go with the development setup in the CakePHP manual. This setup allows you to hit a URL like http://domain.com/cake/appname/controller/action/param1/param2.

    OK, so you have your regular website root directory set up in IIS. Off of the main website, set up a virtual directory called ‘cake’ which points to your main cake directory (e.g. c:\cake). Add the following to {ISAPI_Rewrite Dir}\httpd.conf:

    #This rule performs the ISAPI Rewrite rules
    # catch controller/action urls
    RewriteRule ^/cake/([^/]*)/(.*)/$ /cake/$1/webroot/index.php?url=/$2 [L]

    # catch webroot physical file urls
    RewriteRule ^/cake/([^/]*)/(.*)$ /cake/$1/webroot/$2 [L]

    Now hack your C:\cake\appname\webroot\index.php file and define the WEBROOT as ‘/cake/appname’. For me, it was around line 62:

    if (!defined('WEBROOT_DIR')) {
    define('WEBROOT_DIR', 'cake/appname'); //used to be define('WEBROOT_DIR', basename(dirname(__FILE__)));
    }

    The benefit here is that this enables you to quickly develop in multiple apps off of the cake directory which all use the same cake library without having to recode the httpd.conf file.

  5. Wow, sorry I didn’t respond to this earlier. Thanks for the reply James! You’re totally right. I didn’t modify any of the Cake installation files, but touched only the ISAPI rewrite conf file (which @ the time was named httpd.ini).

    I haven’t looked at Cake in a few months as I’ve been preoccupied with writing for the Django community and investigating Rails. I have a full blown recipe application written in Cake which I’ll have to upgrade soon - so I’ll get back into it once I go through the upgrade process.

  6. One more trick: this one does NOT require the trailing slash to be present on urls to controllers. This one actually checks for the existance of the requested file in webroot, then assumes anything not existing in webroot must be a call to a controller. Again, this is for the development setup that I proposed, not Ryan’s setup which is more a production setup.

    Overwrite any existing cake rules in {ISAPI_Rewrite Dir}\httpd.conf with:

    # oh yeah, we want it
    RewriteEngine On

    #cake

    #This rule performs the ISAPI Rewrite rules
    # catch webroot physical file urls
    RewriteCond %{DOCUMENT_ROOT}/cake/$1/webroot/$2 -f
    RewriteRule ^/cake/([^/]*)/(.*)$ /cake/$1/webroot/$2 [L]

    # catch controller/action urls
    RewriteRule ^/cake/([^/]*)/(.*)$ /cake/$1/webroot/index.php?url=/$2 [L]

    I’ve heard that the %{DOCUMENT_ROOT} variable may not be populated on certain setups. In that case, you’d want to hardcode the path to the cake root directory there.

  7. @James: Totally neat. I’ll most definitely try it out on my Windows box. I’ve updated my post to reflect your very helpful insight. :)

  8. Hello, i’m installing CakePHP on an iis server, i work with pretty URL…
    My index page still working, but all link make me the message “No input file specified”…
    Someone could help me please, it’s for a projetc and a have to install this..

    Tank you

    Sorry for my english, i’m french ;)

Please leave a reply »

Powered by Wordpress. Stalk me.