使用ashx文件合併、壓縮、緩存多個CSS、js或url成一個Response來減小請求次數進而提升頁面加載速度。javascript
實踐證實把js和css分紅多個小文件比放在一個整個大的js或css文件來講更容易維護,可是多個js或css文件會影響網站的性能。但把css或js文件分紅多個小文件頁面加載時,瀏覽器對每一個js或css文件都要創建一個請求,每次請求都會有必定的延遲,假如每次網絡平均延遲100ms 那麼7個css或js文件就要消耗 100*7=700mscss
爲了減小延遲,減小頁面加載時間咱們可使用cdn ,但也能夠把多個文件經過一次請求來獲得。html
引用js文件時能夠寫成下面形式:java
<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文件就要對版本號加一這樣才能獲得最新修改後的文件。jquery
在webconfig中配置成這樣:web
<appSettings> <add key="MasterJS" value="../Scripts/jquery-1.10.2.min.js, ../Scripts/jquery-ui.min.js, ../Scripts/MasterPage.js"/> </appSettings>
目的是經過寫一個HttpCombinerHandler.ashx 在後臺把 這些js文件取到一次返回前臺。c#
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緩存