合并多个css或js请求 来加快页面加载速度

           使用ashx文件合并、压缩、缓存多个CSS、js或url成一个Response来减少请求次数进而提高页面加载速度。

            实践证明把js和css分成多个小文件比放在一个整个大的js或css文件来说更容易维护,但是多个js或css文件会影响网站的性能。但把css或js文件分成多个小文件页面加载时,浏览器对每个js或css文件都要建立一个请求,每次请求都会有一定的延迟,假如每次网络平均延迟100ms 那么7个css或js文件就要消耗 100*7=700ms

合并多个css或js请求 来加快页面加载速度_第1张图片

为了减少延迟,减少页面加载时间我们可以使用cdn ,但也可以把多个文件通过一次请求来得到。

合并多个css或js请求 来加快页面加载速度_第2张图片

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

你可能感兴趣的:(JavaScript,压缩,缓存,调优,合并js,合并css,加快页面加载数度)