posts - 0, comments - 1573, trackbacks - 0

ClientRaw.TXT IIS7 Managed Module Handler

Some background

I guess first off you will be asking what is ClientRaw.txt?

Well this is a file format that a particular software package (Weather Display - http://www.weather-display.com) uses to represent weather data. It is a space delimited file, basically undocumented except for a great tool available at http://www.tnetweather.com/wd-parser.php

So basically this is a simple example of how the data can look  (truncated) :

12345 0.0 0.0 273 6.28 85 1027.7 0.0 9.8 514.2 0.000 0.000 17.1 52 0.0 1 0.0 0 0 2.6 -100.0 255.0 0.0 -100.0 -100.0 -100.0 

So this Weather Display software creates this file and uploads it via FTP to one or more sites. Now clients download this file, using some tool like a Vista Gadget http://weather.cobbnz.com/weather/VistaGadget.aspx that then parses the file and does something useful to it.

I have been working with a colleague from work on a number of projects such as http://weather.cobbnz.com (his) and http://weather.crowe.co.nz (mine), the Vista Gadget and a few other small projects that use this data.

What we found was there was a problem when the FTP site uploads the new file and the clients downloading the file. The FTP site gets updates every 15 seconds and depending on how many clients there are wanting this file you can quite easily end up with getting errors about not being able to open the file because it is in use by another process.

My solution

So my solution was since the site is hosted on IIS 7 was to write a new simple managed module that would cache the file for 15 seconds hopefully reducing the issues we have seen.

So basically the solution is this:

A user requests the file, we have a c# managed module that will attempt to deliver the file to the user using the following logic:

  • Is the file in the cache?
    • If it is in the cache, then deliver it to the client
    • If it is not in the cache, load the file from disk
      • If it successfully reads the file it writes it to another file on disk (secondary cache) and delivers it to the client
      • If it can not read the file from disk it tries to read the secondary cache file
        • If it can read the secondary cache file then it delivers it to the client
        • If it can not read the secondary cache then we return an error

Now remember that the client is requesting a TEXT file, simple static file about 1KB. In IIS 7 when it is using the managed pipeline we can create a module to handle this file and using a single line in web.config have IIS use our module.

In the case I am trying to fix the site is hosted on www.godaddy.com which means we have no real ability to modify the server other than the limited tools they provide and the ability to modify web.config.

 

So now for some code (Note: this is the complete module source code)

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;

namespace ClientRawHandler
{
    public class ClientRawHandler : IHttpHandler
    {
        #region IHttpHandler Members
        public const string CacheKey = "ClientRaw";
        public const string CacheKeyDisk = "ClientRaw.cache";
        public const string CacheKeyDiskErr = "ClientRaw.err";

        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";
            try
            {
                if (context.Cache[CacheKey] == null)
                {
                    // Attempt to read the physical clientraw.txt file
                    string ClientRawFilespec = System.IO.Path.Combine(context.Request.PhysicalApplicationPath, "clientraw.txt");
                    string ClientRawText = System.IO.File.ReadAllText(ClientRawFilespec);

                    if (ClientRawText.StartsWith("12345") == false)
                        throw new Exception();

                    // Save in Application Context in case of problems reading the file after the cache has expired
                    string FileSpec = System.IO.Path.Combine(context.Server.MapPath("/Weather"), CacheKeyDisk);
                    System.IO.File.WriteAllText(FileSpec, ClientRawText);

                    int ClientRawCacheTimeoutInSeconds = Convert.ToInt32(System.Configuration.ConfigurationSettings.AppSettings["ClientRawCacheTimeoutInSeconds"]);
                    context.Cache.Add(CacheKey, ClientRawText, null, DateTime.Now.AddSeconds(ClientRawCacheTimeoutInSeconds), System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.NotRemovable, null);

                    context.Response.Headers.Add("ClientRawHandler", "ReadFromDisk");
                    context.Response.Write(ClientRawText);
                }
                else
                {
                    context.Response.Headers.Add("ClientRawHandler", "ReadFromCache");
                    context.Response.Write(context.Cache[CacheKey].ToString());
                }
            }
            catch(Exception ex)
            {
                try
                {                    
                    
                    // Any errors try to return the cached version of the data from the Application context.
                    string FileSpec = System.IO.Path.Combine(context.Server.MapPath("/Weather"), CacheKeyDisk);
                    if (System.IO.File.Exists(FileSpec) == false)
                    {
                        // No cached version available so return an empty string
                        context.Response.Headers.Add("ClientRawHandler", "NoSecondaryCache");
                        context.Response.Write("-NoCacheVersion- " + FileSpec + " - " + ex.ToString());
                    }
                    else
                    {
                        string Text = System.IO.File.ReadAllText(FileSpec);
                        if (Text.StartsWith("12345") == false)
                        {
                            context.Response.Headers.Add("ClientRawHandler", "InvalidSecondaryCache");
                            context.Response.Write("-CacheVersionEmpty- " + FileSpec + " - " + ex.ToString());
                        }
                        else
                        {
                            context.Response.Headers.Add("ClientRawHandler", "ReadFromSecondaryCache");
                            context.Response.Write(Text);
                        }
                    }
                }
                catch (Exception ex1)
                {
                    context.Response.Headers.Add("ClientRawHandler", "Exception");
                    context.Response.Headers.Add("ClientRawHandlerException", ex1.Message.ToString());
                    context.Response.Write("-Unknown Exception-" + ex1.ToString());

                    string FileSpec1 = System.IO.Path.Combine(context.Server.MapPath("/Weather"), CacheKeyDiskErr);
                    System.IO.File.WriteAllText(FileSpec1, ex1.ToString());
                }
            }
        }

        #endregion
    }
}

We complie this code into a .DLL and copy this to the BIN folder on the web server. We then edit the web.config file so IIS knows how about our module.

Basically we need to add the following:

<system.webServer>
    <handlers>
      <add name="ClientRawHandler" path="clientraw.txt" verb="GET" type="ClientRawHandler.ClientRawHandler,ClientRawHandler" preCondition="integratedMode" />
    </handlers>
</system.webServer>
 

Now that is it.

When a client requests a file identified by the "path" attribute above in our case "clientraw.txt" we will use our handler to process it.

In conclusion

Using this managed module approach has basically stopped the glitches that people were seeing a lot. It can not stop the actual errors because if a file is being written to and you try to read it the error still occurs, but using the secondary cache with the last known good handles this case quite cleanly.

Print | posted on Friday, September 05, 2008 9:19 AM |

Feedback

Gravatar

# re: ClientRaw.TXT IIS7 Managed Module Handler

Great post!
This is what I was looking for :)

It helped me a lot.
Thanks!
4/17/2009 6:46 AM | bsh
Gravatar

# re: ClientRaw.TXT IIS7 Managed Module Handler

thanks admin
Are you really cool
11/30/2009 11:53 AM | tarsus

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 1 and 7 and type the answer here:

Powered by:
Powered By Subtext Powered By ASP.NET