使用ashx文件合并、压缩、缓存多个CSS、js或url成一个Response来减少请求次数进而提高页面加载速度。
实践证明把js和css分成多个小文件比放在一个整个大的js或css文件来说更容易维护,但是多个js或css文件会影响网站的性能。但把css或js文件分成多个小文件页面加载时,浏览器对每个js或css文件都要建立一个请求,每次请求都会有一定的延迟,假如每次网络平均延迟100ms 那么7个css或js文件就要消耗 100*7=700ms
为了减少延迟,减少页面加载时间我们可以使用cdn ,但也可以把多个文件通过一次请求来得到。
引用js文件时可以写成下面形式:
<script type="text/javascript" src ="../services/HttpCombinerHandler.ashx?k=MasterJS&t=text/javascript&v=0"></script>
其中“k=MasterJS”表示webconfig中配置的键值,“t=text/javascript”表示要返回的文件类型,“v=0”表示版本号,因为读取的文件被缓存了(k对应的值和v对应的值作为主键进行缓存的) 所以在调试阶段每次修改了对应的js或css文件就要对版本号加一这样才能得到最新修改后的文件。
在webconfig中配置成这样:
<appSettings> <add key="MasterJS" value="../Scripts/jquery-1.10.2.min.js, ../Scripts/jquery-ui.min.js, ../Scripts/MasterPage.js"/> </appSettings>
目的是通过写一个HttpCombinerHandler.ashx 在后台把 这些js文件取到一次返回前台。
using System.Web; using System.Text; using System.IO; using System.IO.Compression; using System.Web.Caching; using System.Configuration; using System.Net; using Controller; /// <summary> /// 把多个文件打包传到前台 比如多个js或css文件打包成一个请求发送到前台 /// 作者:XingSQ /// 日期:2015-08-26 /// </summary> public class HttpCombinerHandler : IHttpHandler { private readonly static TimeSpan CACHE_DURATION = TimeSpan.FromDays(30); public void ProcessRequest (HttpContext context) { HttpRequest request = context.Request; // string setName = request["k"] ?? string.Empty; //key键 string contentType = request["t"] ?? string.Empty; //内容类型 string version = request["v"] ?? string.Empty; //版本 //判断是否支持gzip压缩 bool isCompressed = this.CanGZip(request); ; UTF8Encoding encoding = new UTF8Encoding(false); //判断请求内容是否在缓存里,在的话直接从cache里读取写入response,否则生成response 并缓存 if (!this.WriteFromCache(context, setName, version, isCompressed, contentType)) { //创建其存储区支持内存的流 using (MemoryStream memoryStream = new MemoryStream(5000)) { //根据response是否支持压缩缓存来指定使用GZipStream或MemoryStream using (Stream writer = isCompressed ? (Stream)(new GZipStream(memoryStream, CompressionMode.Compress)) : memoryStream) { string setDefinition = ConfigurationManager.AppSettings[setName]; string[] fileNames = setDefinition.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); //分别取得指定文件并写入当前流 foreach (string fileName in fileNames) { byte[] fileBytes = this.GetFileBytes(context, fileName); writer.Write(fileBytes, 0, fileBytes.Length); } writer.Close(); } byte[] responseBytes = memoryStream.ToArray(); //缓存 CacheManager.Set(setName + version, responseBytes, CACHE_DURATION); this.WriteBytes(responseBytes, context, isCompressed, contentType); } } } public bool IsReusable { get { return false; } } /// <summary> /// 判断请求内容是否在cache 是的话直接读取发送 /// </summary> /// <param name="context"></param> /// <param name="name"></param> /// <param name="version"></param> /// <param name="isComparessed"></param> /// <returns></returns> private bool WriteFromCache(HttpContext context, string name, string version, bool isComparessed,string contentType) { string cachekey = name + version; if (CacheManager.Exist(cachekey)) { this.WriteBytes((byte[])CacheManager.Get(cachekey), context, isComparessed, contentType); return true; } else { return false; } } /// <summary> /// 判断浏览器知否支持gzip压缩 /// </summary> /// <param name="request"></param> /// <returns>true:支持 false:不支持</returns> private bool CanGZip(HttpRequest request) { string AcceptEncoding = request.Headers["Accept-Encoding"]; // “gzip,deflate” if (string.IsNullOrEmpty(AcceptEncoding)) { return false; } return AcceptEncoding.Contains("gzip"); } /// <summary> /// 取得指定文件内容 /// </summary> /// <param name="context"></param> /// <param name="virtualPath">路径</param> /// <param name="coder"></param> /// <returns></returns> private byte[] GetFileBytes(HttpContext context, string virtualPath/*, Encoder coder*/) { if(virtualPath.StartsWith("http://",StringComparison.InvariantCultureIgnoreCase)) { using( WebClient client = new WebClient()) { return client.DownloadData(virtualPath); } } else { string physicalPath = context.Server.MapPath(virtualPath); byte[] bytes = File.ReadAllBytes(physicalPath); //TODO 转码 return bytes; } } /// <summary> /// 写入输出流,并发送到前台 /// </summary> /// <param name="bytes"></param> /// <param name="context"></param> /// <param name="isCompressed">是否支持压缩</param> /// <param name="contentType">内容类型 比如:text/css、text/javascript</param> private void WriteBytes(byte[] bytes, HttpContext context,bool isCompressed,string contentType) { HttpResponse response = context.Response; response.AppendHeader("Conten-Length",bytes.Length.ToString()); response.ContentType = contentType; if(isCompressed) { response.AppendHeader("Content-Encoding","gzip"); } response.Cache.SetCacheability(HttpCacheability.Public); response.Cache.SetExpires(DateTime.Now.Add(CACHE_DURATION)); response.Cache.SetMaxAge(CACHE_DURATION); response.Cache.AppendCacheExtension(""); response.OutputStream.Write(bytes,0,bytes.Length); response.Flush(); } } //缓存管理类 public class CacheManager { /// <summary> /// 初始化服务器缓存 /// </summary> public static System.Web.Caching.Cache m_cache = HttpRuntime.Cache; public static TimeSpan m_span = new TimeSpan(0,30,0);//30分钟有效 /// <summary> /// 添加缓存信息 /// </summary> /// <param name="key">键</param> /// <param name="value">要缓存的信息</param> public static void Set(string sKey,object oValue) { m_cache.Insert(sKey,oValue, null, System.Web.Caching.Cache.NoAbsoluteExpiration,m_span); } /// <summary> /// 添加缓存,并设置失效时间 /// </summary> /// <param name="sKey">键</param> /// <param name="oValue"></param> /// <param name="timespan">失效时间</param> public static void Set(string sKey, object oValue, TimeSpan timespan) { m_cache.Insert(sKey, oValue, null, System.Web.Caching.Cache.NoAbsoluteExpiration, timespan); } /// <summary> /// 返回指定的缓存信息 /// </summary> /// <param name="key">键</param> /// <returns>缓存信息</returns> public static object Get(string key) { return m_cache[key]; } /// <summary> /// 删除指定键的缓存 /// </summary> /// <param name="key">键</param> /// <returns>缓存信息</returns> public static bool Remove(string key) { return !(null == m_cache.Remove(key)); } /// <summary> /// 判定是否存在知道键的缓存信息 /// </summary> /// <param name="key">键</param> /// <returns>是否存在</returns> public static bool Exist(string key) { return !(null == m_cache[key]); } }
主要过程是 HttpCombinerHandler 通过请求中的k对应的参数“MasterJS”在webconfig中取到要取得哪些文件及其路径。查看缓存,如果缓存中有对应文件那么直接回写response进行输出。如果缓存中没有的话,则取得文件并根据浏览器知否支持gzip压缩进行处理,处理后进行缓存并回写response进行输出。
参考:http://www.codeproject.com/Articles/28909/HTTP-Handler-to-Combine-Multiple-Files-Cache-and-D