华域联盟 .Net Asp.net cookie的处理流程深入分析

Asp.net cookie的处理流程深入分析

一说到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放主要是调用

复制代码 代码如下:

// 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的简单验证

复制代码 代码如下:

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 251951 Cookie 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的BeforeCookieCollectionChange和OnCookieAdd方法,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。

您可能感兴趣的文章:

  • asp.net 操作cookie的简单实例
  • ASP.NET之Response.Cookies.Remove 无法删除COOKIE的原因
  • ASP.NET笔记之页面跳转、调试、form表单、viewstate、cookie的使用说明
  • asp.net中使用cookie与md5加密实现记住密码功能的实现代码
  • asp.net Cookie值中文乱码问题解决方法
  • Asp.net内置对象之Cookies(简介/属性方法/基本操作及实例)
  • asp.net关于Cookie跨域(域名)的问题
  • asp.net中的cookie使用介绍
  • ASP.NET下对cookies的操作实现代码
  • asp.net(C#)跨域及跨域写Cookie问题
  • ASP.NET获取不到JS设置cookies的解决方法
  • asp.net页面状态管理cookie和服务器状态管理Session
  • asp.net下cookies操作完美代码
  • ASP.NET与ASP互通COOKIES的一点经验
  • Asp.net 基于Cookie简易的权限判断
  • asp.net各种cookie代码和解析实例

本文由 华域联盟 原创撰写:华域联盟 » Asp.net cookie的处理流程深入分析

转载请保留出处和原文链接:https://www.cnhackhy.com/45408.htm

本文来自网络,不代表华域联盟立场,转载请注明出处。

作者: sterben

发表回复

联系我们

联系我们

2551209778

在线咨询: QQ交谈

邮箱: [email protected]

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

关注微博
返回顶部