[轉載]Web開發中的緩衝技術:通過ETag實現緩衝處理(Asp.Net)

來源:互聯網
上載者:User
原文地址:Web開發中的緩衝技術:通過ETag實現緩衝處理(Asp.Net) 作者:Selience

IIS已經為我們提供了其內建的緩衝功能。但顯得比較死板,對於更高的要求,IIS的緩衝功能顯然就有些不夠靈活了。

在mvc風格的開發中我們可以通過Filter來定製緩衝方式。

本篇介紹藉助ETag回應標頭實現緩衝,沒有完美的緩衝方案,這種方式能夠準確判斷客戶瀏覽器緩衝是否需要更新,但不會避免伺服器再次產生頁面的過程,它的主要用意在於避免不必要的資料轉送,減少流量緩解頻寬壓力。

何為ETag,以及Is-Non-Match

您可以把ETag理解為HTTP通訊中存在的一個附加資訊,伺服器產生ETag,客戶機瀏覽器下一次再訪問此頁面時會在將此值放在Request Headers中的Is-Non-Match裡。ETag、Is-Non-Match的一個經典用途就是用於緩衝實現。下面會為您詳細說明如何通過ETag在ASP.NET MVC中實現緩衝處理。

關於ETag,您可以去看看W3C的說明:http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

基本原理

伺服器將資料傳給客戶瀏覽器前會對頁面資料進行雜湊計算,並將雜湊值轉為Base64編碼的ASCII字元以存放在Response回應標頭的ETag資訊中。客戶機瀏覽器在接收到來自伺服器的資訊後會將ETag值緩衝到本地,下一次再訪問這個頁面時就會將此ETag值放在Request頭的Is-Non-Match中,伺服器仍然會產生頁面,但要與此ETag值進行比較,如果相同就說明客戶機瀏覽器中緩衝的頁面與即將傳輸的內容一致,進而就不會將重複的頁面傳輸過去而是將狀態置為304,這樣就減少了不必要的頻寬佔用。

如何?

我們要做的是讓伺服器去接受響應並產生資料,但是在將其寫入到流中之前要對即將寫入的內容進行檢查,看其經過Hash計算的值是否與Is-Non-Match中的值相同,如果相同就將HTTP狀態置為304,否則更新ETag並將資料寫入到流中。為實現這個目的,我定義了一個封裝流,將HttpResponseBase.Filter替換為封裝流來方便我們在資料送到客戶機之前獲得資料。

(說明:asp.net mvc的ActionFilter與java裡的那個Filter不太一樣,一開始我以為執行到OnResultExecuted時寫在response裡的內容就已經存在了,後來才發現向Stream裡寫入資料的過程要在OnResultExecuted之後,於是我只好變相在自訂的ResponseWrapper這個Stream封裝流裡去處理response。)

效果展示

 

使用者首次第一次訪問時ETag(_eTag)值為空白,對產生當前內容的雜湊計算結果為“s5vIKNWvqipDyVM46aVWn6QQ0Vg=”,我們在firebug中也能看到返回狀態為200,回應標頭中設有正確的etag值。

 

再次重新整理(注意在IE和firefox中不要按CTRL + F5重新整理,否則瀏覽器會將緩衝清除後發送請求),我們可以看到這次request頭中已經包含了ETag值,如果頁面內容沒有改變的話計算出的ETag值與此值相同。在firefox中可以看到HTTP狀態為304,ETag值保持不變。

原始碼

ActionFilter-ContentCacheFilter代碼

ContentCacheFilter

namespace Sopaco.Lib.Web.Mvc.Filters
{

    public class ContentCacheFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            filterContext.HttpContext.Response.Filter =
                new InternalUse.ResponseWrapper(filterContext.HttpContext.Response.Filter, 
                    filterContext.HttpContext.Request.Headers["If-None-Match"]);
        }
    }
}

 

 

封裝流-ResponseWrapper代碼:

ResponseWrapper

namespace Sopaco.Lib.Web.Mvc.Filters.InternalUse
{
    public class ResponseWrapper : Stream
    {
        #region Fields
        private Stream _innerStream;
        private MemoryStream _memStream;
        private string _eTag = string.Empty;
        #endregion

        #region Constructors
        public ResponseWrapper(Stream stream, string eTag)
        {
            _innerStream = stream;
            _memStream = new MemoryStream();
            _eTag = eTag;
        }
        #endregion

        #region Properties
        public byte[] Data
        {
            get
            {
                _memStream.Position = 0;
                byte[] data = new byte[_memStream.Length];
                _memStream.Read(data, 0, (int)_memStream.Length);
                return data;
            }
        }
        #endregion

        #region overrides of Stream Class
        public override bool CanRead
        {
            get { return _innerStream.CanRead; }
        }

        public override bool CanSeek
        {
            get { return _innerStream.CanSeek; }
        }

        public override bool CanWrite
        {
            get { return _innerStream.CanWrite; }
        }

        public override void Flush()//可能會有這樣一種情況:如果資料比較大則可能在未真正傳輸結束前就要Flush
        {
            var httpContext = HttpContext.Current;
            string currentETag = generateETagValue(Data);
            if(_eTag != null)
            {
                if(currentETag.Equals(_eTag))
                {
                    httpContext.Response.StatusCode = 304;
                    httpContext.Response.StatusDescription = "Not Modified";
                    return;
                }
            }
            httpContext.Response.Cache.SetCacheability(HttpCacheability.Public);
            httpContext.Response.Cache.SetETag(currentETag);
            httpContext.Response.Cache.SetLastModified(DateTime.Now);
            httpContext.Response.Cache.SetSlidingExpiration(true);
            copyStreamToStream(_memStream, _innerStream);
            _innerStream.Flush();
        }

        public override long Length
        {
            get { return _innerStream.Length; }
        }

        public override long Position
        {
            get
            {
                return _innerStream.Position;
            }
            set
            {
                _innerStream.Position = value;
            }
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            return _innerStream.Read(buffer, offset, count);
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _innerStream.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            _innerStream.SetLength(value);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            //_innerStream.Write(buffer, offset, count);
            _memStream.Write(buffer, offset, count);
        }

        public override void Close()
        {
            _innerStream.Close();
        }
        #endregion

        #region private Helper Methods
        private void copyStreamToStream(Stream src, Stream target)
        {
            src.Position = 0;
            int nRead = 0;
            byte[] buf = new byte[128];
            while((nRead = src.Read(buf, 0, 128)) != 0)
            {
                target.Write(buf, 0, nRead);
            }
        }
        private string generateETagValue(byte[] data)
        {
            var encryptor = new System.Security.Cryptography.SHA1Managed();
            byte[] encryptedData = encryptor.ComputeHash(data);
            return Convert.ToBase64String(encryptedData);
        }
        #endregion
    }
}

 

 

應用樣本

[HandleError]
    public class HomeController : Controller
    {
        [ContentCacheFilter]
        //[LazyCacheFilter]
        public ActionResult Index()
        {
            //ViewData["Message"] = DateTime.Now.ToString();
            ViewData["Message"] = "this is from asp.net mvc development server";
            return View();
        }

        public ActionResult About()
        {
            return View();
        }
    }

 

 

ResponseWrapper的實現可能略顯不妥,大家有更好的方案希望多多分享哈^^

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.