conflate.ashx - v1.2
2007-11-13 @ 11:22#
i updated the conflate.ashx
script to include a time-stamp and an optional revision number. this will make make it easier to 'tickle' the cache with a new version id and more reliably output the most recent version of the output to clients. the new query line can now look like this:
conflate.ashx?{v1.6}/css/sample.css,/css/sample2.css,...
the new time-stamp is added to the output, too. this will help with troubleshooting the content. the first line of the output now looks like this:
/* conflated: Tue 13 Nov 2007 16:09:55 GMT {v11} */
below is the latest version of th entire conflate.ashx
script"
<%@ WebHandler Language="C#" Class="Conflate" %> /************************************************************************ * * title: conflate.ashx * version: 1.0 - 2007-10-23 (mca) * version: 1.1 - 2007-10-26 (mca) * - added regex to clean up input url * - added md5 for cache key * - added compression support * 1.2 - 2007-11-13 (mca) * - added optional {version} on queryline * - added 'conflated: (GMT) (version)' comment line at top * * usage: "conflate.ashx?/folder/path/file1.js,/folder/path/file2.js,..." * "conflate.ashx?{v1.1}/folder/path/file1.css,/folder/path/file2.css,..." * * notes: returns a single representation which is a combination of csv list * inserts "error loading ..." msg if file was not found. * ignores "empty" filenames (no load attempts, no errors) * stores results in asp.net cache w/ file dependencies * you modify expires var to control Cache-Control/Expires headers * *************************************************************************/ using System; using System.Web; using System.IO; using System.Text; using System.Web.Caching; using System.Text.RegularExpressions; using System.IO.Compression; public class Conflate : IHttpHandler { const double expires = 60 * 60 * 24 * 30; // 30 days const string cache_control_fmt = "public,max-age={0}"; const string expires_fmt = "{0:ddd dd MMM yyyy HH:mm:ss} GMT"; const string load_err_fmt = "/* error loading {0} */\n"; const string conflated_fmt = "/* conflated: " + expires_fmt + " {1} */\n"; string version = string.Empty; public void ProcessRequest(HttpContext ctx) { string files = ctx.Server.UrlDecode((ctx.Request.Url.Query.Length > 0 ? ctx.Request.Url.Query.Substring(1) : string.Empty)); string ctype = (files.IndexOf(".css") != -1 ? "text/css" : (files.IndexOf(".js") != -1 ? "text/javascript" : string.Empty)); // get version, if it's there try { version = Regex.Match(files, @"(\{.*\})", RegexOptions.IgnoreCase).Value; } catch (Exception ex) { version = string.Empty; } // clean up query line files = Regex.Replace(files, @"(\{.*\})", ""); // drop version files = Regex.Replace(files, "[,]{2,}", ","); // remove duplicate commas files = Regex.Replace(files, "^,(.+)", "$1"); // remove leading comma files = Regex.Replace(files, "(.+),$", "$1"); // remove trailing comma if(ctype!=string.Empty && files!=string.Empty) { string data = LoadFiles(ctx, files.Split(',')); SetCompression(ctx); ctx.Response.Write(data); ctx.Response.StatusCode = 200; ctx.Response.ContentType = ctype; if (expires != 0) { ctx.Response.AddHeader("Cache-Control", string.Format(cache_control_fmt, expires)); ctx.Response.AddHeader("Expires", string.Format(expires_fmt, System.DateTime.UtcNow.AddSeconds(expires))); } } else { ctx.Response.ContentType = "text/plain"; ctx.Response.StatusCode = 404; ctx.Response.StatusDescription = (ctype == string.Empty ? "no valid content-type" : "no files to process"); ctx.Response.Write("\n"); } ctx.Response.End(); } public bool IsReusable { get { return false; } } private string LoadFiles(HttpContext ctx, string[] files) { string data = (string)ctx.Cache.Get(md5(ctx.Request.RawUrl)); if (data == null) { string[] fnames = (string[])files.Clone(); StringBuilder sb = new StringBuilder(); sb.AppendFormat(conflated_fmt, System.DateTime.UtcNow, version); for (int i = 0; i < files.Length; i++) { files[i] = ctx.Server.MapPath(files[i]); if (File.Exists(files[i])) { using (TextReader tr = new StreamReader(files[i])) { sb.AppendLine(tr.ReadToEnd()); } } else { sb.AppendFormat(load_err_fmt, fnames[i]); } } data = sb.ToString(); ctx.Cache.Add( md5(ctx.Request.RawUrl), data, new CacheDependency(files), Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); } return data; } private string md5(string data) { return Convert.ToBase64String(new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(System.Text.Encoding.Default.GetBytes(data))); } private void SetCompression(HttpContext ctx) { string accept = (ctx.Request.Headers["Accept-encoding"] != null ? ctx.Request.Headers["Accept-encoding"] : string.Empty); if (accept.Contains("gzip")) { ctx.Response.Filter = new GZipStream(ctx.Response.Filter, CompressionMode.Compress); ctx.Response.AppendHeader("Content-Encoding", "gzip"); return; } if (accept.Contains("deflate")) { ctx.Response.Filter = new DeflateStream(ctx.Response.Filter, CompressionMode.Compress); ctx.Response.AppendHeader("Content-Encoding", "deflate"); return; } // if no match found return; } }