Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

IIS 6 Kernel Caching from an ISAPI Extension - eek!


:P
On this page:

Arrgh… After talking with some of the IIS team folks yesterday here at PDC about Kernel Caching in IIS, I thought it’d be nice to add this functionality to the Web Connection ISAPI extension. Kernel Caching uses a special ISAPI ‘function’ description HSE_REQ_VECTOR_SEND, which supports setting a few flags that are supposed to have IIS kick in cache the URL internally. What this does is basically cache a dynamic request similar to the way that a static file is cached in IIS.

 

This is server caching, not to be confused with browser or proxy caching. This means IIS is caching the file internally and when another hit to the same URL occurs the URL is served from the cache and not refreshed so the ISAPI extension is not called again until the Cache entry times out. You can set the duration of the cache via an Expires header that can be passed in optionally.

 

The benefit of this of course is that it takes the caching load completely out of the hands of the ISAPI extension, which in the case of Web Connection which is a fairly heavy weight COM server. It provides a performance improvement of an order of magnitude for pages that can truly be cached in this way. On my site this immediately brings a few things to mind, especially the various RSS feeds which are now cached application side.

 

Now, implementing this function is something I looked into a few months ago and gave up because of the horrible, horrible documentation on the Microsoft site. Yes, in hindsight I suppose it makes some sense, but even given that, without the help of a Microsoft dev today at PDC I probably never would have figured this out completely.

 

First things first. Kernel caching is cool but there are number of rules that need to be applied.

 

 

  • Requests must be under 255k
    Actually the value for this is configurable, but there’s a stock value for the result message size. Kernel caching doesn’t kick in for larger files. The value is configurable in the registry (see KB article reference later).
  • Works only with IIS 6
    This feature only works with IIS 6.0 (and possibly IIS 5.1 on XP SP2 – not sure though). Not a big deal except it’s hard to test exact behavior in a non-IIS 6 environment like developing in XP.
  • Requests require Last-modified header
    In order for Kernel Caching to kick in a Last-modified and optionally an Expires header must be provided to IIS. These are used to manage the cache duration.
  • ( Requests cannot have a query string )
    This is a pretty big limitation for dynamic applications, but any page that contains query strings cannot be cached. This means Kernel caching requires either that you create a vanity URL, or simply don’t require a query string for the page. If a querystring is used there will still be browser caching (same user, same request might not hit the server) but not server side caching.
    (the above is according to a KB article that describes limitations. In my own testing it looks like IIS 6 is caching query string requests just fine) 

There are a boatload of more rules that a bit more obscure and not as likely to affect you, but you can find them in this KB Article here:

 

http://support.microsoft.com/?id=817445

 

To use Kernel Caching you use the ISAPI ServerSupportFunction() callback method with the HSE_REQ_VECTOR_SEND option. This option requires that you set up a request structure that breaks out the header and the body of the request and sends it as a single string to the ServerSupport function.

 

The following is a C++ function that takes an full HTTP response including HTTP headers as input splits them up, sets up the various structures required and then calls ServerSupportFunction for caching:

 

/*

CacheOutput

 

Caches output for a full request string in the IIS Kernel Cache.

Assumes you have already made sure that:

 

Header is included in the request

includes Last-Modified and Expires headers

*/

BOOL CacheOutput(EXTENSION_CONTROL_BLOCK *pEcb,char *lpOutput,DWORD cbSize)

{

      HSE_RESPONSE_VECTOR     vec;

      HSE_VECTOR_ELEMENT element;

      DWORD HeaderSize = 0;

      char *strHeader;

     

      // *** find end of header

      char *Content = strstr((const char *) lpOutput,"\r\n\r\n");

      *Content=0; // null terminate

 

      //// *** if we have no header end just send it as part of request

      //// *** Otherwise split it out with

      strHeader = new char[ strlen(lpOutput)  + 1];

      strHeader[0] = 0;

 

      strcpy(strHeader,lpOutput);

      strcat(strHeader,"\r\n\r\n");

 

      Content = Content + 4; // Skip over header breaks

      HeaderSize = strlen( strHeader ) ;

 

      element.ElementType = HSE_VECTOR_ELEMENT_TYPE_MEMORY_BUFFER;

      element.cbOffset = 0;

      element.cbSize = cbSize-HeaderSize;

      element.pvContext = Content;

     

      vec.pszHeaders = strHeader;

      vec.lpElementArray = &element;

      vec.nElementCount = 1;

      vec.pszStatus = "200 OK";

      vec.dwFlags = HSE_IO_FINAL_SEND | HSE_IO_CACHE_RESPONSE | HSE_IO_SEND_HEADERS;

 

      BOOL Result = pEcb->ServerSupportFunction(pEcb,HSE_REQ_VECTOR_SEND,&vec,NULL,NULL);

 

      delete(strHeader);

 

      return TRUE;     

}

 

This function takes a full response string as input and it assumes the content is prefiltered and contains an embedded http header. Typical input looks like this:

 

HTTP/1.1 200 OK

Content-type: text/html

P3P: CP="ALL CRA TAA OWW IND PHY ONL"

Last-Modified: Fri, 16 Sep 2005 04:13:52 GMT

Expires: Fri, 16 Sep 2005 04:23:52 GMT

 

… more HTML

 

which is a typical response that Web Connection might generate to force caching. In this case the cache would be 10 minutes. Notice the Last-Modifed and Expires header, which the Web Connection ISAPI DLL uses as a trigger (in addition to an IIS 6 check) to fire the above method.

 

Note in the code above that you have to make sure to split the HTTP headers off the main content. Make sure that the Headers contain the two trailing CRLFs or else IIS will not interpret the headers and simply write them out as is. That threw me for a loop as I assumed IIS would accept the headers without the trailing CRLFs. Once I added them in the code started caching properly.

 

 

My mainline ISAPI code then checks for a set of conditions (IIS version, Content Length and headers), which if verified cause the code to branch and call the CacheOutput method instead.  This is some internal stuff here, but it should give you an idea of what to check for:

 

// *** If we use Caching - use Vector Send to send the whole file

//          Works only IIS 6.0

if( gnServerMode > 11 &&  ContentLength < 255000 &&

stristr(lpHeader,"last-modified:") &&

stristr(lpHeader,"expires:") )

{

      DWORD Result = 0;

      try

      {

            char *OutputBinary = (char *) new char[  ContentLength + 4 ];

            ReadFromFile(pParam->szOutputFileName,OutputBinary,&ContentLength);

            OutputBinary[ContentLength] = 0; // null terminate

            Result = CacheOutput(ecb,OutputBinary,ContentLength);

            delete(OutputBinary);

      }

      catch(...)

      {

            StandardPage("An error occurred","The application failed to write Cached Output to the Cache Store.",

                                    ecb,"CacheOutputFailure");

     

            Result = FALSE;

            }

     

      return Result;

}

 

 

This is very cool and I have a number of places where this functionality can be applied right away, so I’m stoked.

 

Hopefully this will help make life a little easier for anybody else who needs to use HSE_REQ_VECTOR_SEND and Kernel Caching…

 

Note that for the moment I didn't mess around with Cache invalidation API which is also available.


The Voices of Reason


 

Rick Strahl
September 16, 2005

# re: IIS 6 Kernel Caching from an ISAPI Extension - eek!

Hmmm... maybe I was a little too optimistic on this. It works perfect on my test environment which is my work machine laptop running Windows Server. The pages cache as expected across browser instances (yes I made sure it's not just browser or proxy caching <g>).

So I hooked up the sample and a couple of other places - the RSS feeds mentioned - and pushed the new ISAPI DLL to the server. And - drum roll please - IT DOESN'T WORK!

I can't quite place what's different. I've compared browser and server headers - identical except there are a lot more cookies on the server version (but not anywhere near the 1024k default limit). I even explicitly checked the Metabase key that disables caching - it all looks good, but the caching doesn't work on the server.

Damn - any ideas anybody?

spenser
May 31, 2009

# re: IIS 6 Kernel Caching from an ISAPI Extension - eek!

Ok, it's the cookies that might have(since this is a very old post) ruined the caching.

This would be especially true if the kernel cache evaluates the varies header.

A lot of proxy caches use similar logic, the presence of cookies implies, to use the RFC terminology, that the response is not idempotent. That is, the content of the cookie will influence what has been returned by the server, and hence, it is useless to cache it.

There does not seem to be a published method of getting the kernel cache to ignore this.

Maybe Wade Hilmo, or David Wang would know.

If you ever get this, the security code finally worked :(

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2025