欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-05 09:17:36
一说到cookie我想大家都应该知道它是一个保存在客户端,当浏览器请求一个url时,浏览器会携带相关的cookie达到服务器端,所以服务器是可以操作cookie的,在res...

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

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

第一次请求结果如下:

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

第二次请求结果如下:

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

到这里我们可以看到第二次请求传入的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。