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

2023-07-08 10:41 38次浏览 问答

一说到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。

相关推荐