合併多個css或js請求 來加快頁面加載速度

           使用ashx文件合併、壓縮、緩存多個CSS、js或url成一個Response來減小請求次數進而提升頁面加載速度。javascript

            實踐證實把js和css分紅多個小文件比放在一個整個大的js或css文件來講更容易維護,可是多個js或css文件會影響網站的性能。但把css或js文件分紅多個小文件頁面加載時,瀏覽器對每一個js或css文件都要創建一個請求,每次請求都會有必定的延遲,假如每次網絡平均延遲100ms 那麼7個css或js文件就要消耗 100*7=700mscss

image_16.png

爲了減小延遲,減小頁面加載時間咱們可使用cdn ,但也能夠把多個文件經過一次請求來獲得。html

image_18.png

引用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緩存

相關文章
相關標籤/搜索