Asp.net cookie的处理流程你真的知道吗?

来源:互联网 发布:英雄无敌3 mac 英文版 编辑:程序博客网 时间:2024/05/17 21:51

一说到Cookie我想大家都应该知道它是一个保存在客户端,当浏览器请求一个url时,浏览器会携带相关的Cookie达到服务器端,所以服务器是可以操作Cookie的,在Response时,会把Cookie信息输出到客服端。下面我们来看一个demo吧,代码如下:


第一次请求结果如下:


第二次请求结果如下:

到这里我们可以看到第二次请求传入的Cookie正好是第一次请求返回的Cookie信息,这里的cookie信息的维护主要是我们客户端的浏览器,但是在Asp.net程序开发时,Cookie往往是在服务端程序里面写入,就如我的事例代码;很少有用客服端js实现的。现在我们就来看看asp.net服务端是如何实现读写Cookie的。

首先我们来看看HttpRequest的Cookie是如何定义的:

        public HttpCookieCollection Cookies {
            get {
                EnsureCookies();
                if (_flags[needToValidateCookies]) {
                    _flags.Clear(needToValidateCookies);
                    ValidateCookieCollection(_cookies);
                }
                return _cookies;
            }
        }
 这里的Cookie获取主要是调用一个EnsureCookies方法,EnsureCookies放主要是调用FillInCookiesCollection方法,其中Cookie属性返回的是一个HttpCookieCollection集合,
        // Populates the Cookies property but does not hook up validation.
        internal HttpCookieCollection EnsureCookies() {
            if (_cookies == null) {
                _cookies = new HttpCookieCollection(null, false);
                if (_wr != null)
                    FillInCookiesCollection(_cookies, true /*includeResponse*/);

                if (HasTransitionedToWebSocketRequest) // cookies can't be modified after the WebSocket handshake is complete
                    _cookies.MakeReadOnly();
            }
            return _cookies;
        }

public sealed class HttpCookieCollection : NameObjectCollectionBase
{
    internal HttpCookieCollection(HttpResponse response, bool readOnly) : base(StringComparer.OrdinalIgnoreCase)
    {
        this._response = response;
        base.IsReadOnly = readOnly;
    }
}

 其中这里的FillInCookiesCollection方法实现也比较复杂:

  internal void FillInCookiesCollection(HttpCookieCollection cookieCollection, bool includeResponse) {            if (_wr == null)                 return;            String s = _wr.GetKnownRequestHeader(HttpWorkerRequest.HeaderCookie);             // Parse the cookie server variable.            // Format: c1=k1=v1&k2=v2; c2=...              int l = (s != null) ? s.Length : 0;            int i = 0;             int j;            char ch;            HttpCookie lastCookie = null;             while (i < l) {                 // find next ';' (don't look to ',' as per 91884)                 j = i;                while (j < l) {                     ch = s[j];                    if (ch == ';')                        break;                    j++;                 }                 // create cookie form string                 String cookieString = s.Substring(i, j-i).Trim();                i = j+1; // next cookie start                 if (cookieString.Length == 0)                    continue;                 HttpCookie cookie = CreateCookieFromString(cookieString);                 // some cookies starting with '$' are really attributes of the last cookie                 if (lastCookie != null) {                    String name = cookie.Name;                     // add known attribute to the last cookie (if any)                    if (name != null && name.Length > 0 && name[0] == '$') {                        if (StringUtil.EqualsIgnoreCase(name, "$Path"))                             lastCookie.Path = cookie.Value;                        else if (StringUtil.EqualsIgnoreCase(name, "$Domain"))                             lastCookie.Domain = cookie.Value;                         continue;                     }                }                // regular cookie                 cookieCollection.AddCookie(cookie, true);                lastCookie = cookie;                  // goto next cookie            }             // Append response cookies            if (includeResponse) {                // If we have a reference to the response cookies collection, use it directly                 // rather than going through the Response object (which might not be available, e.g.                // if we have already transitioned to a WebSockets request).                 HttpCookieCollection storedResponseCookies = _storedResponseCookies;                 if (storedResponseCookies == null && !HasTransitionedToWebSocketRequest && Response != null) {                    storedResponseCookies = Response.GetCookiesNoCreate();                 }                if (storedResponseCookies != null && storedResponseCookies.Count > 0) {                    HttpCookie[] responseCookieArray = new HttpCookie[storedResponseCookies.Count];                     storedResponseCookies.CopyTo(responseCookieArray, 0);                    for (int iCookie = 0; iCookie < responseCookieArray.Length; iCookie++)                         cookieCollection.AddCookie(responseCookieArray[iCookie], append: true);                 }                 // release any stored reference to the response cookie collection                _storedResponseCookies = null;            }        } 
说简单一点它主要调用HttpWorkerRequest的GetKnownRequestHeader方法获取浏览器传进来的Cookie字符串信息,然后再把这些信息根据;来分隔成多个HttpCookie实例。把这些HttpCookie实例添加到传进来的HttpCookieCollection参数。

这里HttpWorkerRequest继承结果如下:

internal class ISAPIWorkerRequestInProcForIIS7 : ISAPIWorkerRequestInProcForIIS6
internal class ISAPIWorkerRequestInProcForIIS6 : ISAPIWorkerRequestInProc
internal class ISAPIWorkerRequestInProc : ISAPIWorkerRequest
internal abstract class ISAPIWorkerRequest : HttpWorkerRequest

其中 GetKnownRequestHeader方法的实现主要是在ISAPIWorkerRequest中,其GetKnownRequestHeader主要是调用了它的ReadRequestHeaders私有方法,在ReadRequestHeaders方法中主要是调用它的this.GetServerVariable("ALL_RAW")方法,所以我们可以认为this.GetServerVariable("ALL_RAW")这个方法是获取客户端传来的Cookie参数,而GetServerVariable方法的实现主要是在ISAPIWorkerRequestInProc 类,具体实现非常复杂。

这里的GetKnownRequestHeader方法实现非常复杂我们也就不去深研它了,我们只要知道调用这个方法就会返回Cookie的所有字符串信息。在这个方法里面还调用了一个CreateCookieFromString方法,根据字符串来创建我们的HttpCookie实例。CreateCookieFromString方法实现如下:

  internal static HttpCookie CreateCookieFromString(String s) {             HttpCookie c = new HttpCookie();             int l = (s != null) ? s.Length : 0;             int i = 0;            int ai, ei;             bool firstValue = true;            int numValues = 1;            // Format: cookiename[=key1=val2&key2=val2&...]             while (i < l) {                 //  find next &                 ai = s.IndexOf('&', i);                if (ai < 0)                     ai = l;                // first value might contain cookie name before =                if (firstValue) {                     ei = s.IndexOf('=', i);                     if (ei >= 0 && ei < ai) {                         c.Name = s.Substring(i, ei-i);                        i = ei+1;                     }                    else if (ai == l) {                        // the whole cookie is just a name                        c.Name = s;                         break;                    }                      firstValue = false;                }                 // find '='                ei = s.IndexOf('=', i);                 if (ei < 0 && ai == l && numValues == 0) {                    // simple cookie with simple value                     c.Value = s.Substring(i, l-i);                 }                else if (ei >= 0 && ei < ai) {                     // key=value                    c.Values.Add(s.Substring(i, ei-i), s.Substring(ei+1, ai-ei-1));                    numValues++;                }                 else {                    // value without key                     c.Values.Add(null, s.Substring(i, ai-i));                     numValues++;                }                 i = ai+1;            }             return c;        } 
我们平时很少用到HttpCookie的Values属性,所以这个属性大家还是需要注意一下,这个方法就是把一个cookie的字符串转化为相应的HttpCookie实例。
现在我们回到HttpRequest的Cookies属性中来,这里有一个关于Cookie的简单验证ValidateCookieCollection方法,

 private void ValidateCookieCollection(HttpCookieCollection cc) {
            if (_enableGranularValidation) {
                // Granular request validation is enabled - validate collection entries only as they're accessed.
                cc.EnableGranularValidation((key, value) => ValidateString(value, key, RequestValidationSource.Cookies));
            }
            else {
                // Granular request validation is disabled - eagerly validate all collection entries.
                int c = cc.Count;
 
                for (int i = 0; i < c; i++) {
                    String key = cc.GetKey(i);
                    String val = cc.Get(i).Value;

                    if (!String.IsNullOrEmpty(val))
                        ValidateString(val, key, RequestValidationSource.Cookies);
                }
            }
        }

其中HttpCookieCollection的EnableGranularValidation实现如下:

 internal void EnableGranularValidation(ValidateStringCallback validationCallback)
    {
        this._keysAwaitingValidation = new HashSet<string>(this.Keys.Cast<string>(), StringComparer.OrdinalIgnoreCase);
        this._validationCallback = validationCallback;
    }

    private void EnsureKeyValidated(string key, string value)
    {
        if ((this._keysAwaitingValidation != null) && this._keysAwaitingValidation.Contains(key))
        {
            if (!string.IsNullOrEmpty(value))
            {
                this._validationCallback(key, value);
            }
            this._keysAwaitingValidation.Remove(key);
        }
    }

到这里我们知道默认从浏览器发送到服务器端的Cookie都是需要经过次验证的。这里的ValidateString方法具体实现我们就不说了,不过大家需要知道它是调用了RequestValidator.Current.IsValidRequestString方法来实现验证的,有关RequestValidator的信息大家可以查看HttpRequest的QueryString属性 的一点认识 。现在我们获取Cookie已经基本完成了。那么我们接下来看看是如何添加Cookie的了。

首先我们来看看HttpResponse的Cookie属性:

public HttpCookieCollection Cookies
{
    get
    {
        if (this._cookies == null)
        {
            this._cookies = new HttpCookieCollection(this, false);
        }
        return this._cookies;
    }
}
接下来我们看看HttpCookie的实现如下:

  public sealed class HttpCookie {        private String _name;        private String _path = "/";        private bool _secure;         private bool _httpOnly;        private String _domain;         private bool _expirationSet;         private DateTime _expires;        private String _stringValue;         private HttpValueCollection _multiValue;        private bool _changed;        private bool _added;         internal HttpCookie() {            _changed = true;         }         /*          * Constructor - empty cookie with name         */        /// <devdoc>         ///    <para>        ///       Initializes a new instance of the <see cref='System.Web.HttpCookie'/>         ///       class.         ///    </para>        /// </devdoc>         public HttpCookie(String name) {            _name = name;            SetDefaultsFromConfig();             _changed = true;        }          /*         * Constructor - cookie with name and value          */        /// <devdoc>        ///    <para>         ///       Initializes a new instance of the <see cref='System.Web.HttpCookie'/>        ///       class.         ///    </para>         /// </devdoc>        public HttpCookie(String name, String value) {             _name = name;            _stringValue = value;            SetDefaultsFromConfig();             _changed = true;        }          private void SetDefaultsFromConfig() {            HttpCookiesSection config = RuntimeConfig.GetConfig().HttpCookies;             _secure = config.RequireSSL;            _httpOnly = config.HttpOnlyCookies;            if (config.Domain != null && config.Domain.Length > 0)                 _domain = config.Domain;        }          /*         * Whether the cookie contents have changed          */        internal bool Changed {            get { return _changed; }            set { _changed = value; }         }         /*          * Whether the cookie has been added         */         internal bool Added {            get { return _added; }            set { _added = value; }        }         // DevID 251951Cookie is getting duplicated by ASP.NET when they are added via a native module         // This flag is used to remember that this cookie came from an IIS Set-Header flag,         // so we don't duplicate it and send it back to IIS        internal bool FromHeader {             get;            set;        }         /*         * Cookie name          */         /// <devdoc>         ///    <para>        ///       Gets        ///       or sets the name of cookie.        ///    </para>         /// </devdoc>        public String Name {             get { return _name;}             set {                _name = value;                 _changed = true;            }        }         /*         * Cookie path          */         /// <devdoc>         ///    <para>        ///       Gets or sets the URL prefix to transmit with the        ///       current cookie.        ///    </para>         /// </devdoc>        public String Path {             get { return _path;}             set {                _path = value;                 _changed = true;            }        }         /*         * 'Secure' flag          */         /// <devdoc>         ///    <para>        ///       Indicates whether the cookie should be transmitted only over HTTPS.        ///    </para>        /// </devdoc>         public bool Secure {            get { return _secure;}             set {                 _secure = value;                _changed = true;             }        }        /// <summary>         /// Determines whether this cookie is allowed to participate in output caching.        /// </summary>         /// <remarks>         /// If a given HttpResponse contains one or more outbound cookies with Shareable = false (the default value),        /// output caching will be suppressed for that response. This prevents cookies that contain potentially         /// sensitive information, e.g. FormsAuth cookies, from being cached in the response and sent to multiple        /// clients. If a developer wants to allow a response containing cookies to be cached, he should configure        /// caching as normal for the response, e.g. via the OutputCache directive, MVC's [OutputCache] attribute,        /// etc., and he should make sure that all outbound cookies are marked Shareable = true.         /// </remarks>        public bool Shareable {             get;             set; // don't need to set _changed flag since Set-Cookie header isn't affected by value of Shareable        }         /// <devdoc>        ///    <para>        ///       Indicates whether the cookie should have HttpOnly attribute         ///    </para>        /// </devdoc>         public bool HttpOnly {             get { return _httpOnly;}            set {                 _httpOnly = value;                _changed = true;            }        }         /*          * Cookie domain          */         /// <devdoc>        ///    <para>        ///       Restricts domain cookie is to be used with.        ///    </para>         /// </devdoc>        public String Domain {             get { return _domain;}             set {                _domain = value;                 _changed = true;            }        }         /*         * Cookie expiration          */         /// <devdoc>         ///    <para>        ///       Expiration time for cookie (in minutes).        ///    </para>        /// </devdoc>         public DateTime Expires {            get {                 return(_expirationSet ? _expires : DateTime.MinValue);             }             set {                _expires = value;                _expirationSet = true;                _changed = true;             }        }          /*         * Cookie value as string          */        /// <devdoc>        ///    <para>         ///       Gets        ///       or         ///       sets an individual cookie value.         ///    </para>        /// </devdoc>         public String Value {            get {                if (_multiValue != null)                    return _multiValue.ToString(false);                 else                    return _stringValue;             }             set {                 if (_multiValue != null) {                    // reset multivalue collection to contain                    // single keyless value                    _multiValue.Reset();                     _multiValue.Add(null, value);                }                 else {                     // remember as string                    _stringValue = value;                 }                _changed = true;            }        }         /*          * Checks is cookie has sub-keys          */         /// <devdoc>        ///    <para>Gets a        ///       value indicating whether the cookie has sub-keys.</para>        /// </devdoc>         public bool HasKeys {            get { return Values.HasKeys();}         }         private bool SupportsHttpOnly(HttpContext context) {             if (context != null && context.Request != null) {                HttpBrowserCapabilities browser = context.Request.Browser;                return (browser != null && (browser.Type != "IE5" || browser.Platform != "MacPPC"));            }             return false;        }          /*         * Cookie values as multivalue collection          */        /// <devdoc>        ///    <para>Gets individual key:value pairs within a single cookie object.</para>         /// </devdoc>        public NameValueCollection Values {             get {                 if (_multiValue == null) {                    // create collection on demand                     _multiValue = new HttpValueCollection();                    // convert existing string value into multivalue                    if (_stringValue != null) {                         if (_stringValue.IndexOf('&') >= 0 || _stringValue.IndexOf('=') >= 0)                            _multiValue.FillFromString(_stringValue);                         else                             _multiValue.Add(null, _stringValue);                         _stringValue = null;                    }                }                 _changed = true;                 return _multiValue;             }        }         /*         * Default indexed property -- lookup the multivalue collection         */         /// <devdoc>         ///    <para>         ///       Shortcut for HttpCookie$Values[key]. Required for ASP compatibility.        ///    </para>         /// </devdoc>        public String this[String key]        {            get {                 return Values[key];            }              set {                Values[key] = value;                 _changed = true;            }        }         /*         * Construct set-cookie header          */         internal HttpResponseHeader GetSetCookieHeader(HttpContext context) {            StringBuilder s = new StringBuilder();             // cookiename=            if (!String.IsNullOrEmpty(_name)) {                s.Append(_name);                 s.Append('=');            }              // key=value&...            if (_multiValue != null)                 s.Append(_multiValue.ToString(false));            else if (_stringValue != null)                s.Append(_stringValue);             // domain            if (!String.IsNullOrEmpty(_domain)) {                 s.Append("; domain=");                 s.Append(_domain);            }             // expiration            if (_expirationSet && _expires != DateTime.MinValue) {                s.Append("; expires=");                 s.Append(HttpUtility.FormatHttpCookieDateTime(_expires));            }              // path            if (!String.IsNullOrEmpty(_path)) {                 s.Append("; path=");                s.Append(_path);            }             // secure            if (_secure)                 s.Append("; secure");             // httponly, Note: IE5 on the Mac doesn't support this             if (_httpOnly && SupportsHttpOnly(context)) {                s.Append("; HttpOnly");            }             // return as HttpResponseHeader            return new HttpResponseHeader(HttpWorkerRequest.HeaderSetCookie, s.ToString());         }     }
现在我们回到HttpCookieCollection的Add方法看看,

     public void Add(HttpCookie cookie) {
            if (_response != null)
                _response.BeforeCookieCollectionChange();
 
            AddCookie(cookie, true);

            if (_response != null)
                _response.OnCookieAdd(cookie);
        }
 
public sealed class HttpResponse
{
  internal void BeforeCookieCollectionChange()
   {
    if (this._headersWritten)
    {
        throw new HttpException(SR.GetString("Cannot_modify_cookies_after_headers_sent"));
    }

  }
 internal void OnCookieAdd(HttpCookie cookie)
 {
    this.Request.AddResponseCookie(cookie);
 }
}
public sealed class HttpRequest
{
  internal void AddResponseCookie(HttpCookie cookie)
  {
    if (this._cookies != null)
    {
        this._cookies.AddCookie(cookie, true);
    }
    if (this._params != null)
    {
        this._params.MakeReadWrite();
        this._params.Add(cookie.Name, cookie.Value);
        this._params.MakeReadOnly();
    }
 }
}

到这里我们应该知道每添加或修改一个Cookie都会调用HttpResponse的BeforeCookieCollectionChangeOnCookieAdd方法,BeforeCookieCollectionChange是确认我们的cookie是否可以添加的,以前在项目中就遇到这里的错误信息说什么“在header发送后不能修改cookie”,看见默认情况下_headersWritten是false,那么它通常在哪里被设置为true了,在HttpReaponse的BeginExecuteUrlForEntireResponse、Flush、EndFlush方法中被设置为true,而我们最常接触到的还是Flush方法。这里的OnCookieAdd方法确保Cookie实例同时也添加到HttpRequest中。

  internal void AddCookie(HttpCookie cookie, bool append) {
            ThrowIfMaxHttpCollectionKeysExceeded();
 
            _all = null;
            _allKeys = null;

            if (append) {
                // DevID 251951    Cookie is getting duplicated by ASP.NET when they are added via a native module
                // Need to not double add response cookies from native modules
                if (!cookie.FromHeader) {
                    // mark cookie as new
                    cookie.Added = true;
                }
                BaseAdd(cookie.Name, cookie);
            }
            else {
                if (BaseGet(cookie.Name) != null) {
                    // mark the cookie as changed because we are overriding the existing one
                    cookie.Changed = true;
                }
                BaseSet(cookie.Name, cookie);
            }
        }
         private void ThrowIfMaxHttpCollectionKeysExceeded() {
            if (Count >= AppSettings.MaxHttpCollectionKeys) {
                throw new InvalidOperationException(SR.GetString(SR.CollectionCountExceeded_HttpValueCollection, AppSettings.MaxHttpCollectionKeys));
            }

        }

这里的AddCookie方法也非常简单,不过每次添加都会去检查Cookie的个数是否超过最大值。其实添加Cookie还可以调用HttpResponse的AppendCookie方法,

public void AppendCookie(HttpCookie cookie)
{
    if (this._headersWritten)
    {
        throw new HttpException(SR.GetString("Cannot_append_cookie_after_headers_sent"));
    }
    this.Cookies.AddCookie(cookie, true);
    this.OnCookieAdd(cookie);
}
这里它的实现和HttpCookieCollection的     public void Add(HttpCookie cookie)方法实现一致。
 同样我们也知道这些Cookie是在HttpResponse的GenerateResponseHeadersForCookies方法中被使用,
其中GenerateResponseHeadersForCookies方法的实现如下:

  internal void GenerateResponseHeadersForCookies()        {             if (_cookies == null || (_cookies.Count == 0 && !_cookies.Changed))                 return; // no cookies exist             HttpHeaderCollection headers = Headers as HttpHeaderCollection;            HttpResponseHeader cookieHeader = null;            HttpCookie cookie = null;            bool needToReset = false;             // Go through all cookies, and check whether any have been added             // or changed.  If a cookie was added, we can simply generate a new             // set cookie header for it.  If the cookie collection has been            // changed (cleared or cookies removed), or an existing cookie was             // changed, we have to regenerate all Set-Cookie headers due to an IIS            // limitation that prevents us from being able to delete specific            // Set-Cookie headers for items that changed.            if (!_cookies.Changed)             {                for(int c = 0; c < _cookies.Count; c++)                 {                     cookie = _cookies[c];                    if (cookie.Added) {                         // if a cookie was added, we generate a Set-Cookie header for it                        cookieHeader = cookie.GetSetCookieHeader(_context);                        headers.SetHeader(cookieHeader.Name, cookieHeader.Value, false);                        cookie.Added = false;                         cookie.Changed = false;                    }                     else if (cookie.Changed) {                         // if a cookie has changed, we need to clear all cookie                        // headers and re-write them all since we cant delete                         // specific existing cookies                        needToReset = true;                        break;                    }                 }            }              if (_cookies.Changed || needToReset)             {                // delete all set cookie headers                headers.Remove("Set-Cookie");                 // write all the cookies again                for(int c = 0; c < _cookies.Count; c++)                 {                     // generate a Set-Cookie header for each cookie                    cookie = _cookies[c];                     cookieHeader = cookie.GetSetCookieHeader(_context);                    headers.SetHeader(cookieHeader.Name, cookieHeader.Value, false);                    cookie.Added = false;                    cookie.Changed = false;                 }                 _cookies.Changed = false;             }        } 
这里我们还是来总结一下吧:在HttpWorkerRequest中我们调用GetKnownRequestHeader方法来获取Cookie的字符串形式,然后再将这里的字符串转化为HttpCookie集合供HttpRequest使用,在HttpResponse中的GenerateResponseHeadersForCookies方法中会处理我们的cookie实例,调用cookie的GetSetCookieHeader方法得到HttpCookie对应的字符串值,然后把该值添加到HttpHeaderCollection 集合中(或者修改已有的值)。在获取cookie是这里有一个验证需要我们注意的就是RequestValidator.Current.IsValidRequestString方法。   在添加或修改Cookie是有2个地方的检查(1)检查Cookie的个数是否达到我们配置的cookie最大个数,(2)现在是否已经写入头信息,如果头信息已经写了则不能操作cookie。



原创粉丝点击