Walkthrough: IIS 7.0 Output Caching

by Saad Ladki

Web content can be divided into two categories: static content and dynamic content. Static content does not change from request to request. The content that gets returned to the web browser is always the same. Examples of static content include HTML files, JPG or GIF files.

The other is dynamic content. This is the response that gets generated for dynamic content changes with every request. Examples include ASP.NET or PHP content.

There is a big range between these two categories which includes semi-dynamic content. Imagine a dynamic ASP.NET page that executes a database query. There is no reason to execute this query on every request if the underlying database tables change infrequently.

The IIS Output Caching feature targets semi-dynamic content. It allows you to cache static responses for dynamic requests and to gain tremendous scalability.

Prerequisites

For this walkthrough, you need IIS 7.0 or above on Windows® Vista SP1 or on Windows Server® 2008 Beta 3 or later. This walkthrough also uses ASP.NET 2.0 which must be installed as an optional component in the IIS Setup.

Walkthrough Overview

In the following walkthrough, we use the IIS extensibility interfaces to add language-specific copyright messages to JPG files.

First, we do this by adding a managed handler. Dynamically inserting a copyright message into every JPG file comes with a cost however, because now managed code must be executed for every JPG file.

Next, we install WCAT, an IIS performance analysis tool, to measure the throughput of our JPG copyright handler.

Then we add Output Caching to regain performance degradation incurred by adding our copyright handler.

  1. Create a directory called 'pictures' under the %systemroot%\inetpub\wwwroot directory. Execute the following command in an elevated command shell:

    md %systemdrive%\inetpub\wwwroot\pictures
    
  2. Copy some digital pictures - this walkthrough assumes them to be JPG files - to the new pictures directory.

    Note

    Due to the high Internet Explorer security settings on Windows Server 2008, you might get a security dialog box telling you that the web site is blocked. To download the IIS wallpaper, add wallpaper.iis7.org to the list of trusted sites.

  3. Create an application with the appcmd command-line tool.

    %windir%\system32\inetsrv\appcmd add app -site.name:"Default Web Site" 
    
        -path:/pictures -physicalPath:%systemdrive%\inetpub\wwwroot\pictures
    
  4. Create the directory App_Code underneath the pictures directory:

    md %systemdrive%\inetpub\wwwroot\pictures\App_Code
    
  5. Open Notepad and paste the following code into it.

    using System;
    using System.Web;
    using System.Drawing;
    using System.Drawing.Imaging;
              
    namespace IIS7Demos
    {
        public class imageCopyrightHandler : IHttpHandler
        {
            public void ProcessRequest(HttpContext context)
            {
                string message = "Copyright © IIS 7.0 Team";
                try            {
                    string languageHeader;
             languageHeader = context.Request.Headers["Accept-Language"].Substring(0,2).ToUpper();
                    switch (languageHeader)
                    {
                        case ("DE"):
                            message = "IIS 7.0 Team - Alle Rechte vorbehalten";
                            break;
                        case ("ES"):
                            message = "Marca Registrada IIS 7.0 Team";
                            break;
                        default:
                            break;
                    }
                }
                catch 
               { 
                    // if something fails, e.g. no Language-Accept header, we go with the english message 
               }
                InsertCopyrightMessage (   context, 
                                message, 
                                "yellow"                            
                            );
            }
            void InsertCopyrightMessage(
                                HttpContext context, 
                                string message, 
                                string color
                             )
            {
                try 
                {
                    // get physical path of request 
                    string strPath = context.Request.PhysicalPath;
                    // load as bitmap 
                    Bitmap jpgFile = new Bitmap(strPath);
                    // add copyright message 
                    Graphics g = Graphics.FromImage(jpgFile);
                    Font f = new Font("Arial", 20, GraphicsUnit.Pixel);
                    SolidBrush sb = new SolidBrush(Color.FromName(color));
                    // write copyright message to bitmap 
                    g.DrawString(   message, 
                                    f, 
                                    sb, 
                                    5, 
                                    jpgFile.Height - f.Height - 5
                                );
                    f.Dispose();
                    g.Dispose();
    
                    // save it to response stream 
                    jpgFile.Save(   context.Response.OutputStream, 
                                    System.Drawing.Imaging.ImageFormat.Jpeg
                                );
                    jpgFile.Dispose();
                }
                catch (Exception e)
                {
                    context.Response.Write(e.Message);
                }
            }
              
            public bool IsReusable
            {
                get { return true; }
            }
        }
    }
    

    Save the file as %systemdrive%\inetpub\wwwroot\pictures\App\_Code\imageCopyrightHandler.cs.

  6. Create the handler that executes this code when a JPG file is requested:

    %windir%\system32\inetsrv\appcmd set config /section:system.webServer/handlers 
    
        /+[name='imageCopyrightHandler-Integrated',path='*.jpg',
    
        verb='GET,HEAD',type='IIS7Demos.imageCopyrightHandler',preCondition='integratedMode']
    
  7. We must also enable directory browsing because there is not yet a default document:

    %windir%\system32\inetsrv\appcmd set config "Default Web Site/pictures" 
    
        -section:directoryBrowse -enabled:true
    
  8. Browse to the pictures application by typing in the Internet Explorer address bar: http://localhost/pictures. Click the link to your JPG file in the IIS directory listing. You should see the JPG image with the inserted Copyright message.

  9. Look at the code. You see that the Copyright Message depends on the "Accept-Language" header that the browser sends. If you have a German version of Microsoft Server 2008 installed, you see the copyright message "IIS 7.0 Team - Alle Rechte vorbehalten"; if you have a Spanish language version, you see "Marca Registrada IIS 7.0 Team". In all other cases the copyright message will be "Copyright © IIS 7.0 Team". A way to test this code is to change the "Accept-Language" header Internet Explorer sends:

    • Open "Internet Explorer".
    • Open the "Tools" Menu and click "Internet Options".
    • Click the "Languages" button.
    • Click the "Add…" button and add "es" for Spanish or "de" for German.
    • Move the new language to the top of the list via the "Move Up" button.
    • Browse to http://localhost/pictures/<your_jpg_file>.jpg. The Copyright message has changed to the language you configured.
    • Do not forget to go back into the "Languages" dialog box and reset-- otherwise you might wonder later why you get Spanish or German web pages.

Part II - Performance Test of the imageCopyrightHandler

Once the JPG Copyright Handler works, we must determine how fast our code is. Install an IIS 6.0 Resource Kit Tool to run performance tests:

  1. Download the IIS 6.0 Resource Kit Tools and install them. Do a custom install and install only the Web Capacity Analysis Tool (WCAT). WCAT is the only feature of the IIS 6.0 Resource Kit Tools we need to do our performance tests.

    Note

    Due to the high Internet Explorer security settings on Windows Server 2008, you might get a security dialog box telling you that the web-site is blocked. To download the IIS 6.0 Resource Kit, add *.microsoft.com to the list of trusted sites.

  2. Create a directory called PERFTEST, for example:

    md %systemdrive%\perftest
    
  3. The WCAT controller requires three input files:

    • A script file that tells WCAT which URLs to request. Each URL gets a unique ClassID
    • A distribution file that tells WCAT how the requests should be distributed across the URLs specified in the script file
    • A configuration file that configures the parameters of a particular performance run, for example, the duration of the tests, how many HTTP clients to simulate, etc.

    The Script File

    Create a new file called script.cfg in the perftest directory and paste the following content into it:

    NEW TRANSACTION
        classId = 1
        NEW REQUEST HTTP
            Verb = "GET"
            URL = "http://localhost/pictures/<your image name>.JPG"
    NEW TRANSACTION
        classId = 2
        NEW REQUEST HTTP
            Verb = "GET"
            URL = "http://localhost/pictures/<your image name>.JPG"
    

    Note

    Replace the <your image name> entry with the names of your JPG files. If you have more JPG files, you can add a new transaction. Make sure you give each transaction a new ClassID.

    The Distribution File

    The distribution file tells WCAT how it should weigh requests. With the two URLs above, we do an even 50/50 distribution. Each ClassID gets requested 50% of the time.

    Create a file called %systemdrive%\perftest\distribution.cfg in the perftest directory and paste the following content into it:

    1 50
    2 50
    

    The Configuration File

    Here are recommended parameters for the test:

    • Duration: 30 seconds
    • Warm-up: 5 seconds
    • Cooldown: 5 seconds
    • Simulated Http Clients: 20

    Create a file called config.cfg in the perftest directory and paste the following content into it:

    Warmuptime 5s
    Duration 30s
    CooldownTime 5s
    NumClientMachines 1
    NumClientThreads 20
    
  4. Start the controller by executing the following commands:

    Cd \perftest
    "%programfiles%\IIS Resources\WCAT Controller\wcctl" 
        -c config.cfg -s script.cfg -d distribution.cfg -a localhost
    

    As soon as all clients are connected, the perf test will start.

  5. Because we only have one client, open another elevated command-shell and run the following:

    "%programfiles%\IIS Resources\WCAT Client\wcclient.exe" localhost
    

    To do this with more clients, set NumClientMachines in config.cfg to a higher number, and connect clients to the controller via the wcclient command by specifying the name of the controller machine.

    Example: wcclient MyPerfTestControllerMachine

    Note

    If you do this on a 64-Bit version of Windows, WCAT is installed in the "program files (x86)" directory and you must use %programfiles(x86)% to start WCAT.

  6. Here are the results from the first run:

    ########################################################################
    WCAT Performance Statistics_________________________________
    Server                      :      localhost      ()
    #Transactions               :              3      (HTTP/1.1)
    Total Async Sockets         :             20      (5 WCAT Pool Threads)
    Total Elapsed Time          :             30 Secs (0 Hrs,0 Mins,30 Secs)
    Current Connections         :             20
    Total Connection Attempts   :            436      (   14/Sec)
    Total Connect Errors        :              0      (    0/Sec)
    Total Success Connections   :            436      (   14/Sec)
    Total Consec. Connect Errors:              0      (    0/Sec)
    Total Bytes                 :       32301100      ( 1051 KB/Sec)
    Total Bytes Written         :          32264      (    1 KB/Sec)
    Total Bytes Read            :       32268836      ( 1050 KB/Sec)
    Total Requests              :            436      (   14/Sec)
    Total Responses             :            436      (   14/Sec)
    Total Socket Reads          :           6976      (  232/Sec)
    Total Socket Writes         :            436      (   14/Sec)
    Total Parse Errors          :              0      (    0/Sec)
    Total Socket Errors         :              0      (    0/Sec)
    Total I/O Errors            :              0      (    0/Sec)
    Total Internal Errors       :              0      (    0/Sec)
    Total Time Outs             :              0      (    0/Sec)
    Total 200 OK                :            436      (   14/Sec)
    Total 30X Redirect          :              0      (    0/Sec)
    Total 304 Not Modified      :              0      (    0/Sec)
    Total 404 Not Found         :              0      (    0/Sec)
    Total 500 Server Error      :              0      (    0/Sec)
    Total Bad Status            :              0      (    0/Sec)
    Min. Connect Time           :              0 MS
    Avg. Connect Time           :              0 MS
    Max. Connect Time           :             16 MS
    Min. Resp Time (1st Byte)   :           1281 MS
    Avg. Resp Time (1st Byte)   :           1371 MS
    Max. Resp Time (1st Byte)   :           1578 MS
    Min. Response Time (Last)   :           1281 MS
    Avg. Response Time (Last)   :           1373 MS
    Max. Response Time (Last)   :           1578 MS
    Current Outstanding Connects:              0      (   20 Max)
    Current Waitable Connects   :              0      (   20 Max)
    Total Asynchronous Connects :            531      (    1/Sec)
    Total Discarded Connects    :              0      (    0/Sec)
    ########################################################################
    

    The important number to look at is the requests per second. In this case, we get 14 requests per second.

    A word of caution - the bigger your JPG files, the fewer requests you will see. It is likely that your machine is network-bound: IIS will not be able to handle more requests because the network is saturated with the data you are sending. You see the best results with JPG files in the 200-300 KB range.

Adding Output Caching

The code to dynamically insert the copyright message is fairly slow. Fourteen requests per second is not fast for a web server. IIS performs much better. All you must do is create a caching policy that puts URLs with the JPG extension into the kernel Mode cache. Here is how you add the cache policy:

Adding a Cache Policy via the IIS Management Tool

  1. Navigate to "Administrative Tools" and select "Internet Information Services (IIS) Manager".
  2. Use the tree view on the left side to navigate to the "pictures" application.
  3. Select the "Output Caching Rules" menu item.
  4. Click "Add…" in the "Actions" menu.
  5. Add JPG as the "File extension" to cache.
  6. Select "At time intervals" in the "Monitor cached files" section and enter 00:00:10 as the time interval.
  7. Check the "Headers" check-box and enter "Accept-Language".

Note

The Output Cache User Interface is not available in versions before Windows Vista Service Pack 1.

Adding a Cache Policy via Command-line

To do the same procedure with the appcmd tool, enter the following command:

%windir%\system32\inetsrv\appcmd set config "Default Web Site/pictures" 
    -section:caching /+profiles.[extension='.jpg',duration='00:00:10',
    policy='CacheForTimePeriod',varyByHeaders='Accept-Language']

Repeat the performance run to see what and how the configuration settings change.

  1. Start the controller by executing the following commands:

    Cd \perftest
    "%programfiles%\IIS Resources\WCAT Controller\wcctl" 
        -c config.cfg -s script.cfg -d distribution.cfg -a localhost
    
  2. Start the client with:

    "%programfiles%\IIS Resources\WCAT Client\wcclient.exe" localhost
    

    Note

    If you do this on a 64-Bit version of Windows, WCAT is installed in the program files (x86) directory and you must use %programfiles(x86)% to start WCAT.

Sample Output

########################################################################
WCAT Performance Statistics_________________________________
Server                      :      localhost      ()
#Transactions               :              3      (HTTP/1.1)
Total Async Sockets         :             20      (5 WCAT Pool Threads)
Total Elapsed Time          :             30 Secs (0 Hrs,0 Mins,30 Secs)
Current Connections         :             19
Total Connection Attempts   :          13020      (  434/Sec)
Total Connect Errors        :              0      (    0/Sec)
Total Success Connections   :          13019      (  433/Sec)
Total Consec. Connect Errors:              0      (    0/Sec)
Total Bytes                 :      958045737      (31186 KB/Sec)
Total Bytes Written         :         963406      (   31 KB/Sec)
Total Bytes Read            :      957082331      (31155 KB/Sec)
Total Requests              :          13019      (  433/Sec)
Total Responses             :          13019      (  433/Sec)
Total Socket Reads          :         258283      ( 8609/Sec)
Total Socket Writes         :          13019      (  433/Sec)
Total Parse Errors          :              0      (    0/Sec)
Total Socket Errors         :              0      (    0/Sec)
Total I/O Errors            :              0      (    0/Sec)
Total Internal Errors       :              0      (    0/Sec)
Total Time Outs             :              0      (    0/Sec)
Total 200 OK                :          13019      (  433/Sec)
Total 30X Redirect          :              0      (    0/Sec)
Total 304 Not Modified      :              0      (    0/Sec)
Total 404 Not Found         :              0      (    0/Sec)
Total 500 Server Error      :              0      (    0/Sec)
Total Bad Status            :              0      (    0/Sec)
Min. Connect Time           :              0 MS
Avg. Connect Time           :              0 MS
Max. Connect Time           :             63 MS
Min. Resp Time (1st Byte)   :              0 MS
Avg. Resp Time (1st Byte)   :             33 MS
Max. Resp Time (1st Byte)   :            125 MS
Min. Response Time (Last)   :              0 MS
Avg. Response Time (Last)   :             45 MS
Max. Response Time (Last)   :            141 MS
Current Outstanding Connects:              0      (   20 Max)
Current Waitable Connects   :              0      (   20 Max)
Total Asynchronous Connects :          14093      (  147/Sec)
Total Discarded Connects    :              0      (    0/Sec)
########################################################################

Output Caching Advanced Topics

Performance Counters

To ascertain the performance in the output cache, look at output cache counters in the "Reliability and Performance Monitor". There are many interesting counters. Here is one example of how to use the "Reliability and Performance Monitor" together with the output cache.

  1. On Windows Server 2008, start PERFMON via the Start menu. Go to "Administrative Tools" and click "Reliability and Performance Monitor". On Vista, you find "Administrative Tools" in the Control Panel.
  2. Select "Performance Monitor" in the tree view on the right and click the big "+" sign in the toolbar.
  3. Navigate to the "Web Service Cache" counter and click it to open.
  4. Add the "Total URIs Cached" counter.
  5. Rerun the WCAT test.

You see that the number of cached URIs increases depending on how many items you request during the performance test.

IIS Kernel Mode Caching Rules

The IIS output cache supports two cache policies. The regular output cache policy takes advantage of a cache that resides in an IIS worker process. The other cache policy is a kernel mode cache policy, in which case the cache resides in HTTP.SYS, a kernel-mode driver.

Caching your content in kernel-mode allows your web site to go faster. Modify the configuration of the pictures application to use the kernel-mode cache. Here is how the current configuration looks (%systemdrive%\inetpub\wwwroot\pictures\web.config):

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <directoryBrowse enabled="true" />
        <caching>
            <profiles>
                <add extension=".jpg" policy="CacheForTimePeriod" 
                duration="00:00:10" varyByHeaders="Accept-Language" />
            </profiles>
        </caching>                  
  </system.webServer>
</configuration>

Now change it to use kernel-mode caching:

  1. Open %systemdrive%\inetpub\wwwroot\pictures\web.config.

  2. Change the setting.

    <caching>
        <profiles>
            <add extension=".jpg" policy="CacheForTimePeriod" 
                    duration="00:00:10" varyByHeaders="Accept-Language" />
        </profiles>
    </caching>
    

    to the following:

    <caching>
        <profiles>
                <add extension=".jpg" kernelCachePolicy="CacheForTimePeriod" 
            duration="00:00:10" />
            </profiles>
    </caching>
    

You see that we do not use the varyByHeaders attribute anymore. This is because the kernelModeCache does not support some of the features that the user mode output cache supports.

Limitations of the Kernel Mode Output Cache

There are two significant differences between user mode and kernel mode output cache.

  • Kernel Mode Output cache does not support modules and features that must run in user mode, such as authentication or authorization. Example: If authentication schemes like Basic or Windows authentication are enabled, the cache policy will not work. The content is served but not cached. See "Troubleshoot Caching" on how to find out if the content gets cached. More details on why responses might not get cached in kernel-mode is found in this Knowledge Base article.
  • The Kernel Mode Output Cache supports the varyByHeaders attribute but not varyByQuerystring.

Troubleshoot Caching

Failed Request Event Buffering (FREB) is the best way to find out whether or not your request gets cached. FREB tells you why something does not get cached. Here is a sample of a FREB log. In this case, the HTTPSYS_CACHEABLE event tells you that the request does not get cached because the kernel-mode cache is not enabled.

For more detailed information on how to work with FREB, see Troubleshooting Failed Requests Using Tracing in IIS 7.

Use the following command to find out which content is cached in kernel mode:

netsh http show cachestate

Caching Intricacies

Even if you enable Output Caching, IIS does not immediately cache a request. It must be requested a few times before IIS considers a request to be "cache worthy". Cache worthiness can be configured via the ServerRuntime section described in this MSDN article.

The two properties that determine cache-worthiness are frequentHitTimePeriod and frequentHitThreshold. A request is only cached if more than <frequentHitThreshold> requests for a cacheable URL arrive within the <frequentHitTimePeriod>.

The default setting for frequentHitTimePeriod is 10 seconds.

The default setting for frequentHitThreshold is 2.

In the example above, we put all files with the extension JPG into the output cache. This does not always work because sometimes you want to be more selective and only put a particular document into the output cache. Here is how you do this with your most frequently requested page, your default document:

  1. Create a file called default.aspx in the %systemdrive%\inetpub\wwwroot\pictures directory and add the following code:

    <%=DateTime.Now%>
    
  2. Navigate to "Administrative Tools" and select "Internet Information Services (IIS) Manager".

  3. Use the tree view on the left side to navigate to the "pictures" application.

  4. Click "Content View" at the bottom of the page.

  5. Select your default document, for example, default.aspx page.

  6. Click "Switch to feature view" in the "Actions" menu on the right. Every setting that you configure will now only be applied to the default document.

  7. Open the "Output Caching Rules" setting.

  8. Add ".aspx" as a file extension.

  9. Select "kernel-mode caching" then we can select "At time intervals" and enable "Monitor cached files" and enter 00:00:30 as the time interval.

  10. Browse to http://localhost/pictures with "Internet Explorer". By constantly refreshing the page (press Ctrl+F5 to make sure it does not come from the Browser cache), you see that the time will not change for 30 seconds.

Summary

Using the IIS Output Cache feature for semi-dynamic content can improve your web site. You see a substantial improvement in performance and throughput capacity. A simple configuration change is enough to take advantage of this feature.