艾伟_转载:Cookies揭秘

一,前言

Cookies想必所有人都了解, 但是未必所有人都精通。本文讲解了Cookies的各方面知识, 并且提出来了最佳实践。这是笔者在日常工作中的积累和沉淀。

 

二,基础知识

1.什么是Cookies

Cookie 是一小段文本信息,伴随着用户请求和页面在 Web 服务器和浏览器之间传递。Cookie 包含每次用户访问站点时 Web 应用程序都可以读取的信息。

例如,如果在用户请求站点中的页面时应用程序发送给该用户的不仅仅是一个页面,还有一个包含日期和时间的 Cookie,用户的浏览器在获得页面的同时还获得了该 Cookie,并将它存储在用户硬盘上的某个文件夹中。

以后,如果该用户再次请求您站点中的页面,当该用户输入 URL 时,浏览器便会在本地硬盘上查找与该 URL 关联的 Cookie。如果该 Cookie 存在,浏览器便将该 Cookie 与页请求一起发送到您的站点。然后,应用程序便可以确定该用户上次访问站点的日期和时间。您可以使用这些信息向用户显示一条消息,也可以检查到期日期。

Cookie 与网站关联,而不是与特定的页面关联。因此,无论用户请求站点中的哪一个页面,浏览器和服务器都将交换 Cookie 信息。用户访问不同站点时,各个站点都可能会向用户的浏览器发送一个 Cookie;浏览器会分别存储所有 Cookie。

Cookie 帮助网站存储有关访问者的信息。一般来说,Cookie 是一种保持 Web 应用程序连续性(即执行状态管理)的方法。除短暂的实际交换信息的时间外,浏览器和 Web 服务器间都是断开连接的。对于用户向 Web 服务器发出的每个请求,Web 服务器都会单独处理。但是在很多情况下,Web 服务器在用户请求页时识别出用户会十分有用。例如,购物站点上的 Web 服务器跟踪每位购物者,这样站点就可以管理购物车和其他的用户特定信息。因此,Cookie 可以作为一种名片,提供相关的标识信息帮助应用程序确定如何继续执行。

使用 Cookie 能够达到多种目的,所有这些目的都是为了帮助网站记住用户。例如,一个实施民意测验的站点可以简单地将 Cookie 作为一个 Boolean 值,用它来指示用户的浏览器是否已参与了投票,这样用户便无法进行第二次投票。要求用户登录的站点则可以通过 Cookie 来记录用户已经登录,这样用户就不必每次都输入凭据。

2.Cookies如何存储

Cookies保存在用户的本地机器上,不同的浏览器存储在不同的文件夹中,并且按照域名分别保存。即网站之间的Cookies不会彼此覆盖。

IE浏览器的用户可以通过在本地的文档中找到Cookies的txt文件, 不同操作系统的位置不同,windows server 2003/xp都保存在:

C:\Documents and Settings\Administrator\Cookies 文件夹下。

其中名称txt按照域名保存,比如localhost域下的cookies为:

administrator@localhost[1].txt 或者 administrator@localhost[2].txt

其中后面的[1]和[2]是随着每次保存交替变化的。

3.Cookies如何传递

Cookies的信息是在Web服务器和浏览器之间传递的。保存在Http请求中。

(1)请求页面

在请求一个页面的Http头中,会将属于此页面的本地Cookies信息加在Http头中,注意下面加粗的部分:

GET /Cookies/Test.aspx HTTP/1.1 Host: localhost:1335 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; zh-CN; rv:1.9.1.1) Gecko/20090715 Firefox/3.5.1 GTB5 (.NET CLR 3.5.30729) Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,zh;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Cookie: My.Common.TestCookieInfo=Pkid=999&TestValue=aaabbbcccdddeee 

(2)页面响应

如果页面要求写入Cookies信息,则返回的Http如下,注意加粗的部分:

HTTP/1.x 200 OK Server: ASP.NET Development Server/9.0.0.0 Date: Thu, 06 Aug 2009 03:40:59 GMT X-AspNet-Version: 2.0.50727 Set-Cookie: My.Common.TestCookieInfo=Pkid=999&TestValue=aaabbbcccdddeee; expires=Fri, 07-Aug-2009 03:40:59 GMT; path=/ Cache-Control: private Content-Type: text/html; charset=utf-8 Content-Length: 558 Connection: Close 

4.Cookies如何查看

(1)查看Cookies的txt文件

IE用户可以直接查看Cookies的txt文件。

比如:C:\Documents and Settings\Administrator\Cookies\administrator@localhost[1].txt

 

(2)使用插件

FF下使用Web Developer插件可以很方便的查看、删除和修改Cookies:

插件截图:

 

查看页面Cookies:

 

 

 

三. Cookies高级知识

1.Cookie 的限制

大多数浏览器支持最大为 4096 字节的 Cookie。

浏览器还限制站点可以在用户计算机上存储的 Cookie 的数量。大多数浏览器只允许每个站点存储 20 个 Cookie;注意这里的20个是指主键值,也就是20条Cookies记录,但是每个Cookies记录还可以包含若干子键,下面会详细解释。如果试图存储更多 Cookie,则最旧的 Cookie 便会被丢弃。有些浏览器还会对它们将接受的来自所有站点的 Cookie 总数作出绝对限制,通常为 300 个。

2.Cookies的存储格式

Cookies可以包含一个主键, 主键再包含子键。比如asp.net中获取Cookies的格式是:

Request.Cookies[key][subkey].ToString();

 

其中的key就是主键,subkey就是主键关联的子键。

(1)本地磁盘存储格式:

My.Common.TestCookieInfoPkid=999&TestValue=aaabbbcccdddeeelocalhost/1536305960396830021392234896046430021191*

其中的Pkid=999&TestValue=aaabbbcccdddeee 是Cookies的值,由于使用了subkey=subvalue的格式, 所以此Cookies是包含子键的。

(2)Javascript中的Cookie格式

在Javascript中给的Cookie是一个字符串,通过document.cookies获取。字符格式如下:

My.Common.SubKey=Pkid=999&TestValue=aaabbbcccdddeee; SingleKey=SingleKeyValue

 

上面的字符串包含了两个Cookies,一个是不包含子键的SingleKey, 一个是包含pkid和TextValue两个子键的My.Common.SubKey,两个Cookie通过“;”分割。

(3)Asp.Net中的Cookies格式

和所有的服务器端语言一样,Asp.Net中使用集合类保存Cookies集合:

public sealed class HttpCookieCollection : NameObjectCollectionBase{...}

通过HttpResquest和HttpResponse对象的Cookies属性,可以获取和写入当前页面的Cookies。

 

3.Cookies的内容编码格式

Cookies的值中可以保存除了“;”以外的标点符号。但是不能保存汉字。保存汉字会出现乱码。

所以对于Cookies中的内容要进行统一的编码和解码。为了在浏览器端和服务器端都能够进行解码和编码, 所以要统一使用UTF编码格式。

主要是因为javascript中只能使用UTF编码格式。

 

4.Cookies的Path属性

Cookies的Path属性表示当前的Cookies可以作用在网站的那个路径下。

比如下面的两个同名的Cookies:

允许存在两个同名但是Path不同的Cookies。

无论是服务器端还是客户端,在获取时优先获取本页路径下面的Cookies。

也就是说如果在、/chapter10/路径下面的页面, 获取testKey这个Cookies的值,则只能获取到testValue222222这个值。

 

5.Cookies的过期时间

如果保存Cookies时未设置过期时间, 则Cookies的过期时间为“当前浏览器进程有效”,即和Session一样关闭浏览器后则消失。在asp.net中还可以通过设置HttpCookie对象的过期时间为DateTime.MinValue来指定此Cookies为跟随浏览器生效。(这句话来之不易啊,在脑袋等人的帮助下才查到的。)

如果设置了过期时间并且大于当前时间,则会保存Cookies值。

如果设置了过期时间但是小于等于当前时间,则清除Cookies值。

 

6.Cookies与Session

有时我们会忽略Cookies与Session的关系。但是两者是密不可分的。

Session的唯一标示:SessionID是通常保存在Cookies中的(也可以保存在URL中)。对于Asp.Net而言,SessionID保存在键值为“ASP.NET_SessionId”的Cookies中,如图:

因为Cookies的存储数量是有限制的,所以我们的系统在保存Cookies的时候一定要注意防止冲掉这一个关键的Cookies。在下文介绍的最佳实践-以强对象方式保存Cookies的代码中特意对这个Cookies做了处理。

注意,在客户端使用javascript脚本无法获取“ASP.NET_SessionId”的Cookies, 因为此Cookies在服务器端设置了HttpOnly属性为true。

ASP.Net中HttpCookie对象的HttpOnly 属性 指定一个Cookie 是否可通过客户端脚本访问。不能通过客户端脚本访问为 true;否则为 false。默认值为 false。此属性并不能完全阻止客户端在本地获取cookies,但是可以增加通过脚本直接获取的难度。

Microsoft Internet Explorer 版本 6 Service Pack 1 和更高版本支持 Cookie 属性 HttpOnly

7.Cookies加密

在设置Cookies的属性时,有一个选项Secure用来控制Cookie的加密特性。

如果通过 SSL 连接 (HTTPS) 传输 Cookie,则为 true;否则为 false。默认为 false。

如果我们保存一个Cookies并设置加密,那么在非HTTPS的页面中,无论是使用javascript还是服务器端都无法获得此Cookies。但是在本地依然可以看到此Cookies的存在。

 

8.Cookies与Ajax

如果Ajax请求访问一个服务器页面,此服务器页面是可以向用户浏览器写入Cookies和Session的。

 

四. Cookies最佳实践

在了解了Cookies的相关知识后,下面提出最佳的事件方法。其中包括客户端和服务器端两部分。

(1)Asp.Net 中保存Cookies

通常,我们使用Request和Response对象来直接操作Cookies:

写入Cookies:

Response.Cookies["k1"].Value = "k1Value";Response.Cookies["k2"]["k2-1"] = "k2-1Value";Response.Cookies.Add(new HttpCookie("k3", "k3Value"));

 

读取Cookies:

Request["k1"] ;Request.Cookies["k1"].Value ;Request.Cookies["k2"]["k2-1"];Request.Cookies.Get(0).Value;

 

注意Request["k1"]这个大家熟悉的获取get和post参数的方法,同时还能够获取Cookies的值!

另外上面语句中的有些是必须通过Value属性访问的,有些则不需要。

(2)以对象方式保存Cookies

下面提供一个可以以对象方式整体保存Cookies的工具类。并且只占用一条Cookies,所有的属性都存在子键上。

  • 源代码:  
    /// /// Cookies基类。将需要保存Cookies的数据类此类派生,可以将强类型对象在Cookies中的保存和读取。/// /// /// 2009.8.6    ziqiu.zhang     created/// /// /// 假设MyCookiesInfo是从 从Cookies中获取对象:/// ///     CookieInfo item = new CookieInfo(); //new以后已经从Cookies中构造了对象。/// /// 将对象保存在Cookies中:/// ///     CookieInfo item = new CookieInfo();///     item.value = "test value";///     item.SetCookies("1"); //Cookies有效期为1天/// ///     [System.Serializable]public class CookieInfo    {

#region ==================== Constructed Method ====================/// /// 构造函数/// public CookieInfo()        {

}#endregion

#region ==================== Public Method ====================/// /// 得到当前Cookies的过期时间/// /// 过期时间public DateTime GetExpiresTime()        {string cookieName = GetType().ToString();if (HttpContext.Current.Request.Cookies[cookieName] != null)            {return HttpContext.Current.Request.Cookies[cookieName].Expires;            }return DateTime.MinValue;        }

/// /// 保存Cookies,过期时间为浏览器关闭则失效。/// /// Cookies过期事件/// 是否保存成功public bool Save()        {return this.Save(DateTime.MinValue);        }

/// /// 保存Cookies,需要指定过期时间。/// /// Cookies过期事件/// 是否保存成功public bool Save(DateTime expiresTime)        {string CookieName = GetType().ToString();            HttpCookie SessionCookie = null;

//对 SessionId 进行备份.if (HttpContext.Current.Request.Cookies["ASP.NET_SessionId"] != null)            {string SesssionId = HttpContext.Current.Request.Cookies["ASP.NET_SessionId"].Value.ToString();                SessionCookie = new HttpCookie("ASP.NET_SessionId");                SessionCookie.Value = SesssionId;

}//设定cookie 过期时间.            DateTime dtExpiry = expiresTime;            HttpContext.Current.Response.Cookies[CookieName].Expires = dtExpiry;

//设定cookie 域名.string domain = string.Empty;if (HttpContext.Current.Request.Params["HTTP_HOST"] != null)            {//domain = "www.elong.com";                domain = HttpContext.Current.Request.Params["HTTP_HOST"].ToString();            }

//如果是www.elong.com或多级域名,需要转化为elong.comif (domain.IndexOf(".") > -1)            {string[] temp = domain.Split('.');

if (temp.Length >= 3)                {                    domain = temp[temp.Length - 2].Trim() + "." + temp[temp.Length - 1].Trim();                }

HttpContext.Current.Response.Cookies[CookieName].Domain = domain;            }

//把类的属性, 写入Cookie.            PropertyInfo[] Propertys = GetType().GetProperties();

foreach (PropertyInfo pi in Propertys)            {object oj = pi.GetValue(this, null);                Type type = pi.PropertyType;string valueStr = string.Empty;

if (oj != null && oj.ToString() != string.Empty)                {if (type == Type.GetType("System.DateTime"))                    {                        valueStr = ((DateTime)oj).ToString("yyyy/MM/dd HH:mm:ss", System.Globalization.DateTimeFormatInfo.InvariantInfo);                    }else                    {                        valueStr = oj.ToString();                    }

HttpContext.Current.Response.Cookies[CookieName][pi.Name] = HttpUtility.UrlEncode(valueStr);                }

}

//如果cookie总数超过20 个, 重写ASP.NET_SessionId, 以防Session 丢失.if (HttpContext.Current.Request.Cookies.Count > 20 && SessionCookie != null)            {if (SessionCookie.Value != string.Empty)                {                    HttpContext.Current.Response.Cookies.Remove("ASP.NET_SessionId");                    HttpContext.Current.Response.Cookies.Add(SessionCookie);                }            }

return true;        }

/// /// 找回Cookie值/// public void Load()        {string cookieValue = string.Empty;string CookieName = GetType().ToString();

//通过遍历属性, 从cookie 中找回值, 回写到属性.            PropertyInfo[] Propertys = GetType().GetProperties();

foreach (PropertyInfo pi in Propertys)            {try                {                    cookieValue = HttpUtility.UrlDecode(HttpContext.Current.Request.Cookies[CookieName][pi.Name].ToString());                }catch                {                    cookieValue = string.Empty;                }

if (pi.CanWrite && cookieValue != null && cookieValue != string.Empty)                {try                    {object obb = cookieValue;                        Type type = pi.PropertyType;

obb = Convert.ChangeType(obb, type);                        pi.SetValue(this, obb, null);                    }catch { }

}            }

}#endregion

}
  • 使用

首先说明如何使用此类。

为想要保存在Cookies中的类建立模型,并且继承自CookieInfo即可。比如下面建立了MyCookieInfo类,其中包含属性pkid,TestValue和TestDateTime:

    /// /// 保存Cookies的数据对象///     [System.Serializable]public class MyCookieInfo : CookieInfo    {    private int m_Pkid = 0; public int Pkid        {            get            {return m_Pkid ;            }            set            {                m_Pkid = value ;            }        }

private string m_TestValue = "";public string TestValue        {            get            {return m_TestValue;            }            set            {                m_TestValue = value;            }        }

private DateTime m_TestDateTime = DateTime.Now;public DateTime TestDateTime        {            get            {return m_TestDateTime;            }            set            {                m_TestDateTime = value;            }        }    }

 

接下来就可以使用对象的Save和Load方法保存和读取Cookies:

  • 保存

    Save方法有两个重载,不带参数的Save方法表示Cookies的过期时间与浏览器相同,即浏览器关闭则Cookies消失。否则需要传入Cookies过期时间。
    MyCookieInfo testCookies = new MyCookieInfo();    testCookies.Pkid = 1;    testCookies.TestValue = "中文测试";testCookies.Save(); 
  • 读取
    MyCookieInfo testCookies = new MyCookieInfo();testCookies.Load();this.lblMsg.Text = "Pkid:" + testCookies.Pkid.ToString();this.lblMsg.Text += ",TestValue:" + testCookies.TestValue.ToString();this.lblMsg.Text += ",TestDateTime:" + testCookies.TestDateTime.ToString("yyyy/MM/dd HH:mm:ss", System.Globalization.DateTimeFormatInfo.InvariantInfo);

现在我们已经可以将一个强类型的对象读取和保存Cookies了。

 

 

(3)使用Javascript操作Cookies

在客户端我们同样需要操作Cookies。

下面是封装了的专门用于操作Cookies的jQuery工具函数。如果还有人不知道jQuery是什么,请参考我的“从零开始学习jQuery”系列教程:

http://www.cnblogs.com/zhangziqiu/archive/2009/04/30/jQuery-Learn-1.html

当然此工具函数稍加修改,就可以变成标准的Javascript函数。

下载地址:http://files.cnblogs.com/zhangziqiu/jquery.extend-lastest.js

工具函数说明:

方法签名: jQuery.cookie(name, subName, value,  options)

方法说明:读取、写入、删除Cookies

方法参数:

名称 说明 举例
name cookies的主键值 读取主键:
$.cookie("singleKey")

写入cookies,值为字符串:

$.cookie("singleKey", "", "singleKey-value", { expires: 1, path: "/", secure: false })
subName 子键名称。在写入时请传递空或者null 读取子键:
$.cookie("multiKey", "subName1")

写入cookies,值为对象:

var subNameObj = { subName1: "aaa", subName2: "bbb", subName3: "ccc" };
$.cookie("multiKey", "", subNameObj, { expires: 1, path: "/", secure: false });
value Cookies值,可以是字符串或者对象。

如果是对象,则将对象的每个属性保存在Cookies子键。
参见上面实例。
options 参数:

expires:可以是数字或者Data类型的对象。

如果传入数字表示几天后过期。

path:路径,默认为域名根目录(“/”)。

secure:是否启用加密,默认为否。


指定过期时间:

var myDate = new Date();myDate.setFullYear(2009, 10, 10);$.cookie("singleKey", "", "singleKey-value", { expires: myDate, secure: false })
 

1天后过期:

var time = Date();
$.cookie("singleKey", "", "singleKey-value", { expires: 1, path: "/", secure: false })

 

五.总结

很久没有发表文章了,作为博客园改版后我的第一篇文章, 希望对大家的工作能有所帮助。 欢迎拍砖!

时间: 2024-10-14 17:52:51

艾伟_转载:Cookies揭秘的相关文章

艾伟_转载:把委托说透(1):开始委托之旅 委托与接口

委托,本是一个非常基础的.NET概念,但前一阵子在园子里却引起轩然大波.先是Michael Tao的随笔让人们将委托的写法与茴香豆联系到了一起,接着老赵又用一系列文章分析委托写法的演变,并告诫"嘲笑孔乙己的朋友们,你们在一味鄙视"茴"的四种写法的同时,说不定也失去了一个了解中国传统文化的机会呢!". 在我个人看来,委托是.NET Framework中一个非常炫的特性,绝不会向有些评论里说的那样,根本没有机会接触.恰恰相反,我们几乎每天都会接触委托,使用委托. 其实园

艾伟_转载:数组排序方法的性能比较(上):注意事项及试验

昨天有朋友写了一篇文章,其中比较了List的Sort方法与LINQ中排序方法的性能,而最终得到的结果是"LINQ排序方法性能高于List.Sort方法".这个结果不禁让我很疑惑.因为List.Sort方法是改变容器内部元素的顺序,而LINQ排序后得到的是一个新的序列.假如两个排序方法的算法完全一致,LINQ排序也比对方多出元素复制的开销,为什么性能反而会高?如果LINQ排序的算法/实现更为优秀,那为什么.NET Fx不将List.Sort也一并优化一下呢?于是今天我也对这个问题进行了简

艾伟_转载:一个MVC分页Helper

本人写的一个分页Helper,支持普通分页(也就是,首页.上一页.下一页.末页等),综合分页(普通分页和数字分页的综合).下面是分页效果: 分页代码: PagerHelper.cs 代码   1 using System;  2  using System.Collections.Generic;  3 using System.Collections.Specialized;  4 using System.Linq;  5 using System.Web;  6 using System.

艾伟_转载:把委托说透(2):深入理解委托

在上一篇随笔中我们通过示例逐步引入了委托,并比较了委托和接口.本文将重点剖析委托的实质. 委托在本质上仍然是一个类,我们用delegate关键字声明的所有委托都继承自System.MulticastDelegate.后者又是继承自System.Delegate类,System.Delegate类则继承自System.Object.委托既然是一个类,那么它就可以被定义在任何地方,即可以定义在类的内部,也可以定义在类的外部. 正如很多资料上所说的,委托是一种类型安全的函数回调机制, 它不仅能够调用实

艾伟_转载:WCF版的PetShop之三:实现分布式的Membership和上下文传递

本系列文章导航 WCF版的PetShop之一:PetShop简介 WCF版的PetShop之二:模块中的层次划分 WCF版的PetShop之三:实现分布式的Membership和上下文传递 通过上一篇了解了模块内基本的层次划分之后,接下来我们来聊聊PetShop中一些基本基础功能的实现,以及一些设计.架构上的应用如何同WCF进行集成.本篇讨论两个问题:实现分布式的Membership和客户端到服务端上下文(Context)的传递. 一. 如何实现用户验证 对登录用户的验证是大部分应用所必需的,对

艾伟_转载:从ASP.NET的PHP执行速度比较谈起

上星期我在InfoQ发表了一篇新闻,对Joe Stagner在博客上发表的三篇关于ASP.NET与PHP性能对比的文章进行了总结.写新闻其实挺不爽的,因为不能夹杂个人的看法,只能平铺直叙陈述事实.当然,如果像某些新闻那样"换一种说法"是可以骗过一些"不明真相的群众",但是这就有违道德了.因此,在客观陈述完新闻内容之后,我只能选择把自己的感想.评论等内容放在自己的博客上. Joe Stagner的背景挺特殊,它是PHP的老用户,在ASP.NET出现之前就是PHP的重量

艾伟_转载:在Mono中创建ASP.NET程序

一只可爱的猴子: 一次偶然的机会碰到这只猴子,并在工作中也用到它了.现将自己的使用经验分享与此(以OpenSUSE为例介绍). 对于不熟悉Mono的朋友来说,Mono就是.Net在Linux和其它操作系统上的实现(Unix.Mac.iphone.Windows...).Mono的官方网站是:http://www.mono-project.com 严格来讲,Mono是一个开源的.跨平台的C#语言和其CLR的实现,并与微软的.NET二进制兼容.很绕口吧,这是Mono官方网站写的:An open so

艾伟_转载:基于.NET平台的Windows编程实战(五)—— 问卷管理功能的实现

本系列文章导航 基于.NET平台的Windows编程实战(一)--前言 基于.NET平台的Windows编程实战(二)-- 需求分析与数据库设计 基于.NET平台的Windows编程实战(四)-- 数据库操作类的编写 基于.NET平台的Windows编程实战(五)-- 问卷管理功能的实现 基于.NET平台的Windows编程实战(六)-- 题目管理功能的实现 首先,为了使我们的界面更加便于操作及布局,我们引入第三开源控件DockPanel[当然也可以不引入控件而直接进行开发],你可以从这里:Do

艾伟_转载:WCF安全之EndPointIdentity

最近在做一个项目,应用了WCF进行分布式开发,中间还涉及到消息路由器等,好在有WCF提供了强大的基础支持,当然,本身也作了不少的扩展,实际,我 最关心的是WCF的安全问题,网上不少朋友介绍的WCF的安全也是少得可怜,微软发布的WCF Security GUID好像讲得也只是入门级别的教程,离真正应用到项目中还是有很大的距离,这也让我萌发了分享的想法,今天先放出来占个位置吧,有反对的朋友砖头轻 点,呵~,可以告诉你,WCF的安全里,有很多的小秘密,当然还是要告诉你,并且有此小秘密是要自己去体验后才