javascript--9客户端检测

客户端存储
1.cookie
①最初是在客户端用于存储会话信息的。
1.1 限制
①cookie在性质上是绑定在特定的域名下的。当设定了一个cookie后,再给创建它的域名发送请求时,都会包含这个cookie。
②cookie的限制:
□IE6以及更低版本限制每个域名最多20个cookie。
□IE7和之后版本每个域名最多50个cookie。
□Firefox50个
□Opera50个
□Safari和Chrome无硬性规定
③cookie尺寸限制:4096字节(加减1)的长度限制。尺寸限制到一个域下所有的cookie,而非每个cookie单独限制。
1.2 cookie的成分
名称、值、域、路径、失效时间、安全标志。
1.3 JavaScript中的cookie
JavaScript操作cookie是通过BOM的document.cookie属性。
①当用来获取属性时,document.cookie返回当前页面可用的所有cookie的字符串,一系列由分号隔开的名-值对。
name1=value;name2=value2;name3=value3
所有名字和值都经URL编码,所以必须使用decodeURIComponent()来解码。
②用于设置值时,document.cookie属性可以设为一个新cookie字符串。设置document.cookie并不会覆盖cookie,除非设置的cookie名已经存在。设置前必须用encodeURIComponent()编码。
③没有删除cookie的直接方法。需要使用相同的路径、域和安全选项再次设置cookie,并将失效时间设为过去的时间。
□cookie读取、写入和山粗功能:
var CookieUtil = {
get : function(name){
var cookieName = encodeURIComponent(name) + "=",
cookieStart = document.cookie.indexOf(cookieName),
cookieValue = null;
if(cookieStart > -1){
var cookieEnd = document.cookie.indexOf(";",cookieStart)
if(cookieEnd == -1){
cookieEnd = document.cookie.length;
}
cookieValue = decodeURIComponent(document.cookie.substring(cookieStart+cookieName.length,cookieEnd));
}
return cookieValue;
},
set : function(name, value, expires, path, domain, secure){
var cookieText = encodeURIComponent(name) + "=" + encodeURIComponent(value);
if(expires instanceof Date){
cookieText += ";expires=" + expires.toGMTString();
}
if(path){
cookieText += "; path=" + path;
}
if(domain){
cookieText += "; domain=" + domian;
}
if(secure){
cookieText += "; secure";
}
document.cookie = cookieText;
},
unset : function(name, path, domain, secure){
this.set(name, "", new Date(0), path, domain, secure);
}
};
1.4 子cookie
①子cookie是存放单个cookie中更小段的数据。也就是使用cookie值来存储多个名称-值对。
name=name1=value1&name2=value2&name3=value3
□操作子cookie,get、getAll、set、setAll、unset、unsetAll:
var subCookieUtil = {
get : function(name, subName){
var subCookies = this.getAll(name);
if(subCookies){
return subCookies[subName];
}else{
return null;
}
},
getAll : function(name){
var cookieName = encodeURIComponent(name) + "=",
cookieStart = document.cookie.indexOf(cookieName),
cookieValue = null,
result = {};
if(cookieStart > -1){
var cookieEnd = document.cookie.indexOf(";",cookieStart);
if(cookieEnd == -1){
cookieEnd = document.cookie.length;
}
cookieValue = document.cookie.substring(cookieStart + cookieName.length,cookieEnd);
if(cookieValue.length > 0){
var subCookies = cookieValue.split("&");
for(var i=0, len=subCookies.length; i<len; i++){
var parts = subCookies[i].split("=");
result[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]);
}
return result;
}
}
return null;
},
Set : function(name, subName, value, expires, path, domain, secure){
Var subCookies = this.getAll(name) || {};
Subcookies[subName] = value;
This.setAll(name, subcookies, expires, path, domain, secure);
},
setAll : function(name, subcookies, expires, path, domain, secure){
var cookieText = encodeURIComponent(name) + "=";
var subcookieParts = new Array();
for(var subName in subcookies){
if(subName.length>0 && subcookies.hasOwnProperty(subName)){
subcookieParts.push(encodeURIComponent(subName) + "=" + encodeURIComponent(subcookies[subName]));
}
}
if(cookieParts.length>0){
cookieText += subcookieParts.join("&");
if(expires instanceof Date){
cookieText += ";expires=" + expires.toGMTString();
}
if(path){
cookieText += ";path=" + path;
}
if(domain){
cookieText += ";path" + path;
}
if(secure){
cookieText += ";secure";
}
}else{
cookieText += ";expires=" + (new Date(0)).toGMTString();
}
document.cookie = cookieText;
},
unset : function(name, subName, path, domain, secure){
var subcookies = this.getAll(name);
if(subcookies){
delete subcookies[subName];
this.setAll(name, subcookies, null, path, domain, secure);
}
},
unsetAll : function(name, path, domain, secure){
this.setAll(name, null, new Date(0), path, domain, secure);
}
}
2.IE用户数据(不太实用,略之)
3.DOM存储机制
①DOM存储两个目标
□提供一种在cookie之外存储会话数据的途径。
□提供一种存储大量可以跨越会话存在的数据的机制。
②支持情况:
□Firefox2支持部分
□IE8+、Safari3.1+、Chrome1.0+、Firefox3.1+全部实现。
3.1 存储类型
①Storage类型用来存储最大限(因浏览器而异)的名值对。Storage的实例和其他对象行为一样,有下列额外的方法。
□clear():删除所有值。
□getItem(name):根据指定的名字name获取相应的值。
□key(index):在指定的数字位置获取该位置的名字。
□removeItem(name):删除由名字name指定的名值对。
□setItem(name, value):为指定的名字name设置一个对应的值。
□可通过属性访问值。
3.2 sessionStorage对象
①sessionStorage对象存储特定于某个会话的数据,也即数据只保存到浏览器关闭。存储在sessionStorage中的数据可以跨越页面刷新而存在。
②sessionStorage对象绑定于某个服务器会话,所以文件在本地运行时不可用。存储在sessionStorage中数据只能由最初给对象存储数据的页面访问到,对多页面应用有限制。
③sessionStorage对象是Storage类型的实例。
3.3 globalStorage对象
①只在Firefox2中实现。跨越会话存储数据,并且有特定的访问限制。
//保存数据
globalStorage["wrox.com"].name = "Nicholas";
//获取数据
var name = globalStorage["wrox.com"].name;
3.4 localStorage对象
①localStorage上不能指定任何访问性规则;规则事先设定好了。为了能访问到同一个localStorage对象,页面必须来自同一个域名(子域名无效),使用同一种协议,在同一个端口上。
②数据保留到通过JavaScript删除或者是用户清除浏览器缓存。
用例:
localStorage.setItema("name","Nicholas");
localStorage.book = "Profession JavaScript";
var name = localStorage.getItem("name");
var book = localStorage.book;
③兼容globalStorage:
function getLocalStorage(){
if(typeof localStorage == "object"){
return localStorage;
}else if(typeof globalStorage == "object"){
retrun globalStorage[location,host];
}else{
throw new Error("no localstorage");
}
3.5 StorageItem类型
①Storage对象中所有存储的每个项目都是StorageItem的实例
□value属性:被存储的值。
□secure属性:只有脚本使用HTTPS协议访问页面才可设置。通过https访问默认为true。
3.6 storage事件
对storage对象进行修改,都会在文档上触发storage事件。其事件对象event有以下属性:
□domain:进行变更的存储的域名。
□key:进行设置或者是删除的键名。
□newValue:要将该键设为的值,如果是删除则为null。
□oldValue:被更改之前的值。
3.7 限制
DOM存储的限制也和浏览器相关。


一、能力检测(特性检测)
先检测达成目的的最常用的特性。比如想要根据ID提取某元素,先检测document.getElementById(), 再检测document.all[id]。
更可靠的能力监测是检测某个特性是否按照适当方式行事,而非仅仅是是否存在。比如检测某个方法是否存在:
function isHostMethod(object, property) {
     var t = typeof object[property];
     return t == 'function'  ||  (!!(t == 'object' && object[property])) || t == 'unknow';
}

使用示例: isHostMethod(document, "getElementById");  isHostMethod(new String(), "slice"); 第二个参数是字符串。
能力检测不是浏览器检测。
二、怪癖检测
怪癖检测的目标是识别浏览器存在什么缺陷。最好在脚本的一开始就执行此类检测。
三、用户代理检测
用户代理检测通过检测用户代理字符串来确定实际使用的浏览器。在每一次http请求过程中,用户代理字符串是作为响应首部发送的,而且该字符串可以通过js的navigator.userAgent属性访问。在服务器端,通过检测用户代理字符串来确定用户使用的浏览器是一种常用切广为接受的做法。而在客户端,用户代理检测是万不得已才用的方法,优先级排在特性检测及怪癖检测之后。
确切知道浏览器的名字和版本号不如确切地知道它使用的是什么呈现引擎。
能力检测类似,怪癖检测(quirks detection)的目标是识别浏览器的特殊行为。但与能力检测确
认浏览器支持什么能力不同,怪癖检测是想要知道浏览器存在什么缺陷( “怪癖”也就是 bug) 。这通常
需要运行一小段代码,以确定某一特性不能正常工作。例如,IE8 及更早版本中存在一个 bug,即如果
某个实例属性与 [[Enumerable]] 标记为 false 的某个原型属性同名,那么该实例属性将不会出现在
fon-in 循环当中。可以使用如下代码来检测这种“怪癖” 。
var hasDontEnumQuirk = function(){
var o = { toString : function(){} };
for (var prop in o){if (prop == "toString"){
return false;
}
}
return true;
}();

以上代码通过一个匿名函数来测试该“怪癖” ,函数中创建了一个带有 toString() 方法的对象。
在正确的 ECMAScript 实现中, toString 应该在 for-in 循环中作为属性返回。
另一个经常需要检测的“怪癖”是 Safari 3 以前版本会枚举被隐藏的属性。可以用下面的函数来检
测该“怪癖” 。
var hasEnumShadowsQuirk = function(){
var o = { toString : function(){} };
var count = 0;
for (var prop in o){
if (prop == "toString"){
count++;
}
}
return (count > 1);
}();

如果浏览器存在这个 bug,那么使用 for-in 循环枚举带有自定义的 toString() 方法的对象,就
会返回两个 toString 的实例。
一般来说, “怪癖”都是个别浏览器所独有的,而且通常被归为 bug。在相关浏览器的新版本中,这
些问题可能会也可能不会被修复。由于检测“怪癖”涉及运行代码,因此我们建议仅检测那些对你有直
接影响的“怪癖” ,而且最好在脚本一开始就执行此类检测,以便尽早解决问题。

检测浏览器的方式

  1、对象特征检测法:判断浏览器能力的通用方法。如果更关注浏览器的能力而不在乎它的实际身份,就可以使用这种检测方法。常见的原生Ajax写法中就用这种方法来创建XMLHttpRequest:

IXHR: function(){
if(window.ActiveXObject){
XHR=new ActiveXObject('Microsoft.XMLHTTP');
}else if(window.XMLHttpRequest){
XHR=new XMLHttpRequest();
}else{
return null;
}
}

2、user-agent字符串检测法:通过能浏览器的user-agent字符串进行解析来判断,判断方法在下文的detect.js中

检测平台/操作系统

  通过navigator折platform属性和user-agent字符串来判断,判断方法在下文的detect.js中

检测浏览器和操作系统-detect.js

var sUserAgent = navigator.userAgent;
var fAppVersion = parseFloat(navigator.appVersion);

function compareVersions(sVersion1, sVersion2) {
var aVersion1 = sVersion1.split(".");
var aVersion2 = sVersion2.split(".");

if (aVersion1.length > aVersion2.length) {
for (var i=0; i < aVersion1.length - aVersion2.length; i++) {
aVersion2.push("0");
}
} else if (aVersion1.length < aVersion2.length) {
for (var i=0; i < aVersion2.length - aVersion1.length; i++) {
aVersion1.push("0");
}
}

for (var i=0; i < aVersion1.length; i++) {

if (aVersion1[i] < aVersion2[i]) {
return -1;
} else if (aVersion1[i] > aVersion2[i]) {
return 1;
}
}

return 0;
}
var isOpera = sUserAgent.indexOf("Opera") > -1;
var isMinOpera4 = isMinOpera5 = isMinOpera6 = isMinOpera7 = isMinOpera7_5 = false;
if (isOpera) {
var fOperaVersion;
if(navigator.appName == "Opera") {
fOperaVersion = fAppVersion;
} else {
var reOperaVersion = new RegExp("Opera (//d+//.//d+)");
reOperaVersion.test(sUserAgent);
fOperaVersion = parseFloat(RegExp["$1"]);
}
isMinOpera4 = fOperaVersion >= 4;
isMinOpera5 = fOperaVersion >= 5;
isMinOpera6 = fOperaVersion >= 6;
isMinOpera7 = fOperaVersion >= 7;
isMinOpera7_5 = fOperaVersion >= 7.5;
}
var isKHTML = sUserAgent.indexOf("KHTML") > -1
|| sUserAgent.indexOf("Konqueror") > -1
|| sUserAgent.indexOf("AppleWebKit") > -1;

var isMinSafari1 = isMinSafari1_2 = false;
var isMinKonq2_2 = isMinKonq3 = isMinKonq3_1 = isMinKonq3_2 = false;
if (isKHTML) {
isSafari = sUserAgent.indexOf("AppleWebKit") > -1;
isKonq = sUserAgent.indexOf("Konqueror") > -1;
if (isSafari) {
var reAppleWebKit = new RegExp("AppleWebKit///(//d+(?://.//d*)?)");
reAppleWebKit.test(sUserAgent);
var fAppleWebKitVersion = parseFloat(RegExp["$1"]);
isMinSafari1 = fAppleWebKitVersion >= 85;
isMinSafari1_2 = fAppleWebKitVersion >= 124;
} else if (isKonq) {
var reKonq = new RegExp("Konqueror///(//d+(?://.//d+(?://.//d)?)?)");
reKonq.test(sUserAgent);
isMinKonq2_2 = compareVersions(RegExp["$1"], "2.2") >= 0;
isMinKonq3 = compareVersions(RegExp["$1"], "3.0") >= 0;
isMinKonq3_1 = compareVersions(RegExp["$1"], "3.1") >= 0;
isMinKonq3_2 = compareVersions(RegExp["$1"], "3.2") >= 0;
}

}
var isIE = sUserAgent.indexOf("compatible") > -1
&& sUserAgent.indexOf("MSIE") > -1
&& !isOpera;

var isMinIE4 = isMinIE5 = isMinIE5_5 = isMinIE6 = false;
if (isIE) {
var reIE = new RegExp("MSIE (//d+//.//d+);");
reIE.test(sUserAgent);
var fIEVersion = parseFloat(RegExp["$1"]);
isMinIE4 = fIEVersion >= 4;
isMinIE5 = fIEVersion >= 5;
isMinIE5_5 = fIEVersion >= 5.5;
isMinIE6 = fIEVersion >= 6.0;
}
var isMoz = sUserAgent.indexOf("Gecko") > -1
&& !isKHTML;
var isMinMoz1 = sMinMoz1_4 = isMinMoz1_5 = false;
if (isMoz) {
var reMoz = new RegExp("rv:(//d+//.//d+(?://.//d+)?)");
reMoz.test(sUserAgent);
isMinMoz1 = compareVersions(RegExp["$1"], "1.0") >= 0;
isMinMoz1_4 = compareVersions(RegExp["$1"], "1.4") >= 0;
isMinMoz1_5 = compareVersions(RegExp["$1"], "1.5") >= 0;
}
var isNS4 = !isIE && !isOpera && !isMoz && !isKHTML
&& (sUserAgent.indexOf("Mozilla") == 0)
&& (navigator.appName == "Netscape")
&& (fAppVersion >= 4.0 && fAppVersion < 5.0);
var isMinNS4 = isMinNS4_5 = isMinNS4_7 = isMinNS4_8 = false;
if (isNS4) {
isMinNS4 = true;
isMinNS4_5 = fAppVersion >= 4.5;
isMinNS4_7 = fAppVersion >= 4.7;
isMinNS4_8 = fAppVersion >= 4.8;
}
var isWin = (navigator.platform == "Win32") || (navigator.platform == "Windows");
var isMac = (navigator.platform == "Mac68K") || (navigator.platform == "MacPPC")
|| (navigator.platform == "Macintosh");
var isUnix = (navigator.platform == "X11") && !isWin && !isMac;
var isWin95 = isWin98 = isWinNT4 = isWin2K = isWinME = isWinXP = false;
var isMac68K = isMacPPC = false;
var isSunOS = isMinSunOS4 = isMinSunOS5 = isMinSunOS5_5 = false;
if (isWin) {
isWin95 = sUserAgent.indexOf("Win95") > -1
|| sUserAgent.indexOf("Windows 95") > -1;
isWin98 = sUserAgent.indexOf("Win98") > -1
|| sUserAgent.indexOf("Windows 98") > -1;
isWinME = sUserAgent.indexOf("Win 9x 4.90") > -1
|| sUserAgent.indexOf("Windows ME") > -1;
isWin2K = sUserAgent.indexOf("Windows NT 5.0") > -1
|| sUserAgent.indexOf("Windows 2000") > -1;
isWinXP = sUserAgent.indexOf("Windows NT 5.1") > -1
|| sUserAgent.indexOf("Windows XP") > -1;
isWinNT4 = sUserAgent.indexOf("WinNT") > -1
|| sUserAgent.indexOf("Windows NT") > -1
|| sUserAgent.indexOf("WinNT4.0") > -1
|| sUserAgent.indexOf("Windows NT 4.0") > -1
&& (!isWinME && !isWin2K && !isWinXP);
}
if (isMac) {
isMac68K = sUserAgent.indexOf("Mac_68000") > -1
|| sUserAgent.indexOf("68K") > -1;
isMacPPC = sUserAgent.indexOf("Mac_PowerPC") > -1
|| sUserAgent.indexOf("PPC") > -1;
}
if (isUnix) {
isSunOS = sUserAgent.indexOf("SunOS") > -1;
if (isSunOS) {
var reSunOS = new RegExp("SunOS (//d+//.//d+(?://.//d+)?)");
reSunOS.test(sUserAgent);
isMinSunOS4 = compareVersions(RegExp["$1"], "4.0") >= 0;
isMinSunOS5 = compareVersions(RegExp["$1"], "5.0") >= 0;
isMinSunOS5_5 = compareVersions(RegExp["$1"], "5.5") >= 0;
}
}

尽量不使用客户端检测。先设计最通用的方案,再使用特定的浏览器方法增强该方案
能力检测
检测浏览器是否具备某一能力。
尽可能使用typeof进行能力检测,对所要使用的函数或者属性进行是否符合预期的检测,可以降低出错的风险
并不需要知道用户使用的是什么浏览器,只需要知道用户的浏览器是否具备某些开发所需的功能即可,毕竟浏览器的功能是会改变的,并不能保证现在独有的属性未来不会有其他浏览器实现。
怪癖检测
检测浏览器的特殊行为,与能力检测类似,不过,怪癖检测是想要知道浏览器存在什么缺陷。
用户代理检测
通过检测用户代理字符串来确定实际使用的浏览器。在每一次的http请求过程中,用户代理字符串是作为响应首部发送的,可以通过js的navigator.userAgent属性访问。这服务端这是常见而广为接受的做法,客户端是万不得已的做法。
识别呈现引擎
注意检测五大呈现引擎:IE,Gecko,WebKit,KHTML和Opera
注意识别顺序Opera,WebKit,KHTML,Gecko,IE,前一个引擎有包含后一个引擎的某些属性,顺序错误将不能正确识别
识别浏览器 识别平台 识别windows操作系统 识别移动设备 识别游戏系统 完整的用户代理检测代码
//完整代码
varclient = function(){ //呈现引擎
varengine ={ ie:0, gecko:0, webkit:0, khtml:0, opera:0, //完整版本号
ver:null
}; //浏览器
varbrowser ={ //主要浏览器
ie:0, firefox:0, safari:0, konq:0, opera:0, chrome:0, //具体版本号
ver:null
}; //平台、设备和操作系统
varsystem ={ win:false, mac:false, x11:false, //移动设备
iphone:false, ipod:false, ipad:false, ios:false, android:false, nokiaN:false, winMobile:false, //游戏系统
wii:false, ps:false
}; //检测呈现引擎和浏览器
varua =navigator.userAgent; if(window.opera){ engine.ver = browser.ver =window.opera.version(); engine.opera = browser.opera =parseFloat(engine.ver); }elseif(/AppleWebKit\/(\S+)/.test(ua)){ engine.ver = RegExp["$1"]; engine.webkit =parseFloat(engine.ver); //确定是Chrome还是Safari
if(/Chrome\/(\S+)/.test(ua)){ browser.ver = RegExp["$1"]; browser.chrome =parseFloat(browser.ver); }elseif(/Version\/(\S+)/.test(ua)){ browser.ver = RegExp["$1"]; browser.safari =parseFloat(browser.ver); }else{ //近似地确定版本号
elsevarsafariVersion = 1; if(engine.webkit < 100){ safariVersion = 1; }elseif(engine.webkit < 312){ safariVersion = 1.2; }elseif(engine.webkit < 412){ safariVersion = 1.3; }else{ safariVersion = 2; } browser.safari = browser.ver =safariVersion; } }if(/KHTML\/(S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)){ engine.ver = browser.ver = RegExp["$1"]; engine.khtml = browser.konq =parseFloat(engine.ver); }elseif(/rv:([^\)]+)\) Gecko\/\d{8}/.test(ua)){ engine.ver = RegExp["$1"]; browser.gecko =parseFloat(engine.ver); //确定是不是Firefox
//if(/Firefox\/(\S+)/.test(ua)){ browser.ver = RegExp["$1"]; browser.firefox =parseFloat(browser.ver); } }elseif(/MSIE ([^;]+)/.test(ua)){ engine.ver = browser.ver = RegExp["$1"]; engine.ie = browser.ie =parseFloat(engine.ver); } 检测浏览器
browser.ie =engine.ie; browser.opera =engine.opera; //检测平台
varp =navigator.platform; system.win = p.indexOf("Win") == 0; system.mac = p.indexOf("Mac") == 0; system.x11 = (p == "X11") || (p.indexOf("Linux") == 0); //检测windows操作系统
if(system.win){ if(/Win(?:dows )?([^do]{2})\s?(\d+\.\d+)?/.test(ua)){ if(RegExp["$1"] == "NT"){ switch(RegExp["$2"]){ case"5.0": system.win = "2000"; break; case"5.1": system.win = "XP"; break; case"6.0": system.win = "Vista"; break; case"6.1": system.win = "7"; break; default: system.win = "NT"; break; } }elseif(RegExp["$1"] == "9x"){ system.win = "ME"; }else{ system.win = RegExp["$1"]; } } } //移动设备
system.iphone = ua.indexOf("iPhone") > -1; system.ipod = ua.indexOf("iPod") > -1; system.ipad = ua.indexOf("iPad") > -1; system.nokiaN = ua.indexOf("NokiaN") > -1; //windows mobile
if(system.win == "CE"){ system.winMobile =system.win; }elseif(system.win == "Ph"){ if(/Windows Phone OS (\d+.\d+)/.test(ua)){ system.win = "Phone"; system.winMobile = parseFloat(RegExp["$1"]); } } //检测IOS版本
if(system.mac && ua.indexOf("Mobile") > -1){ if(/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)){ system.ios = parseFloat(RegExp.$1.replace("_",".")); }else{ system.ios = 2; //不能真正检测出来,所以只能猜测,猜测低版本会更适当点
} } //检测Android版本
if(/Android (\d+\.\d+)/.test(ua)){ system.android = parseFloat(RegExp.$1); } //游戏系统
system.wii = ua.indexOf("Wii") > -1; system.ps = /playstation/i.test(ua); //返回这些对象
return{ engine:engine, browser:browser, system:system }; }();
使用方法
使用情形:
1、不能直接准确地使用能力检测或怪癖检测
2、同一款浏览器在不同平台下具备不同的能力
3、为了跟踪分析等目的需要知道确切的浏览器

Cookie
HTTP Cookie,通常直接叫做cookie,最初是在客户端用于存储会话信息的。该标准要求服务器对任意HTTP请求发送Set-Cookie HTTP头作为响应的一部分,其中包含会话信息。

HTTP/1.1 200 OK

Content-type:text/html

Set-Cookie:name=value

Other-header: other-heater-value

这个HTTP响应设置以name为名称、以value为值的一个cookie,名称和值在传送时都必须是URL编码的。浏览器会存储这样的会话信息,并在这之后,通过每个请求添加Cookie HTTP头将信息发送回服务器:

GET /index.html HTTP/1.1

Cookie: name=value

Other-header: other-header-value

发送回服务器的额外信息可以用于唯一验证客户来自于发送的哪个请求。

限制
cookie在性质上是绑定在特定域名下的。当设定一个cookie后,在再给创建它的域名发送请求时,都会包含这个cookie。这个限制确保了存储在cookie中的信息只能让批准的接受者访问,而无法被其他域访问。

每个域的cookie总数是有限的,不过浏览器之间各有不同。

1、IE6及更低版本限制每个域名最多20个cookie;

2、IE7和以后版本每个域名最多50个,IE7最初是20个,后来通过补丁修复了;

3、Firefox限制每个域最多50个cookie;

4、Opera限制每个域最多30个cookie;

5、Safari和Chrome对于每个域的cookie数量限制没有硬性规定。

当超过单个域名限制后还要再设置cookie,浏览器会清除以前设置的cookie。IE和Opera会删除最近最少使用过的(LRU,least recently used)cookie,Firefox看上去是随机删除,所以考虑cookie限制非常重要。

大小限制:大多数浏览器限制为4096B(加减1),为了最佳的浏览器兼容性,最好将整个cookie长度限制在4095B以内。

cookie的构成
cookie由浏览器保存的以下几块信息构成:

1、名称:唯一确定cookie的名称。cookie名称是不区分大小写的。由于某些服务器在处理cookie时是区分大小写的,所以在实践中最好将cookie名称看作是区分大小写的。

2、值:储存在cookie中的字符串值。值必须被URL编码。

3、域:cookie对于哪个域是有效的。所有向该域发送的请求中都会包含这个cookie信息。这个值可以包含子域,也可以不包含。如果没有明确设定,那么这个域被认为来自设置cookie的这个域。

4、路径:对于指定域中的那个路径,应该向服务器发送cookie。例如:你可以指定cookie只有从http://www.guokr.com/blog/中才能访问,那么http://www.guokr.com/的页面就不会发送cookie信息,即使请求都是来自同一个域。

5、失效时间:表示cookie何时应该被删除的时间戳。默认情况下,浏览器会话结束时即将所有cookie删除;不过也可以自己设置删除时间。如果你设置的时间是个以前的时间,则cookie会被立刻删除。

6、安全标志:指定后,cookie只有在使用SSL连接的时候才发送到服务器。例如,cookie信息只能发送给https://www.guokr.com,而http://www.guokr.com的请求则不能发送cookie。

7、HttpOnly:指定后,浏览器只能通过HTTP(或HTTPS)请求来获取cookie信息,通过非HTTP的方式是不可访问的,比如Javascript(“document.cookie”),这样可以防止cross-site scripting(XSS)窃取cookie信息。

每一段信息都作为Set-Cookie头的一部分,使用分号加空格分隔每一段,如下例所示。

HTTP/1.1 200 OK

Content-type:text/html

Set-Cookie:name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; domain=.guokr.com

Other-header: other-heater-value

该头信息指定一个叫做name的cookie,它会在格林威治时间2007年1月22日7:10:24失效,同时对于www.guokr.com和guokr.com的任何子域都有效。

secure标志和HttpOnly标志是cookie中非名值对的部分,直接包含一个secure单词和HttpOnly标志:

HTTP/1.1 200 OK

Content-type:text/html

Set-Cookie:name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; domain=.guokr.com; path:/; secure; HttpOnly

Other-header: other-heater-value

尤其要注意,域、路径失效时间和secure标志都是服务器给浏览器的指示,以指定何时应该发送cookie。这些参数并不会作为发送到服务器的cookie信息的一部分,只有名值对才会被发送。

Javascript中的cookie
在Javascript中处理cookie有些复杂,主要原因是document.cookie属性。这个属性的独特之处在于它会因为使用它的不同而表现出不同的行为。

当用来获取属性值时,document.cookie返回当前页面可用的(根据cookie的域、路径、失效时间和安全设置)所有cookie的字符串,一系列由分号隔开的名值对:

name1=value1;name2=value2;name3=value3
所有名字和值都是经过URL编码的,所以必须使用decodeURIComponent()来编码。

当用于设置值时候,document.cookie属性可以设置为一个新的cookie字符串。这个cookie字符串会被解释并添加到现有的cookie集合中。设置document.cookie时,如果cookie的名称已经存在,则会覆盖。设置cookie的格式如下,和Set-Cookie头中使用的格式一样。

name=value; expires=expiration_time; path=domain_path; domain=domain_name; secure
这些参数中,只有cookie的名字和值是必需的。

当客户端每次向服务器端发送请求的时候,都会发送这个cookie;当浏览器关闭的时候,它就会被删除。虽然这段代码没问题,但因为这里正好名称和值无需编码,所以最好每次设置cookie时都像下面这个例子中一样使用encodeURIComponent(),并且要添加额外的信息,只要将参数追加到该字符串。

document.cookie = encodeURIComponent(‘name’) + ‘=’ + encodeURIComponent(‘value’) + '; domain=.guokr.com; path=/';
由于javascript中读写cookie不是非常直观,常常需要写一些函数来简化cookie的功能:

var cookieUtil = {

/**

* 获取cookies中key为name的值

* @param {string} name key值

* @return {string} 返回对应值,如果没有则返回''

*/

get : function(name) {

if (document.cookie.length > 0) {

var e, s = document.cookie.indexOf(name + '=');

if (s != -1) {

s = s + name.length + 1;

e = document.cookie.indexOf(';', s);

if (e == -1) {

e = document.cookie.length;

}

return unescape(document.cookie.substring(s, e));

}

}

return '';

},

/**

* 设置cookies的值

* @param {string} name key值

* @param {string} val 值

* @param {object} expired 过期时间的date对象

* @param {string} path 路径对象

* @param {object} 对象本身

*/

set : function(name, val, expired, path) {

document.cookie = name + '=' + escape(val) + ((expired == null) ? '' : ';expires=' + expired.toGMTString()) + ';path=' + (path || '/');

return this;

},

/**

* 删除cookies

* @param {string} name

* @param {string} path

*/

remove : function(name, path) {

var exp = new Date();

exp.setTime(exp.getTime() - 1); // 设置过期时间

var val = this.get(name);

if (val != null) {

document.cookie = name + "=" + val + ";expires=" + exp.toGMTString() + ';path=' + (path || '/');

}

}

};

子cookie
为了绕开浏览器的单域名下的cookie数限制,一些开发人员使用了一种称为子cookie的概念。子cookie是存放在单个cookie中的更小段的数据。也就是使用cookie值来存储多个名称值对:

name=name1=value1&name2=value2&name3=value3&name4=value4
子cookie一般也以查询字符串的格式进行格式化。然后这些只可以使用单个cookie进行存储和访问,而非对每个名值对使用不同的cookie存储。最后网站或者Web应用程序可以无需达到单域名cookie上限也可以存储更加结构化的数据。

获取子cookie方法需要在上述方法上做一些扩展,但不复杂。

5、关于cookie的思考

还有一类cookie被称为“HTTP专有cookie”。这样的cookie可以从浏览器或者服务器设置,但是只能从服务器端读取,因为Javascript无法获取HTTP专有cookie的值,只能通过HTTP(HTTPS)请求来获取cookie值。

由于所有的cookie都会由浏览器作为请求头发送,所以在cookie中存储大量信息会影响到特定域的请求性能。cookie信息越大,完成对服务器的请求时间也就越长。所以还是尽可能在cookie中少存储信息。

一定不要在cookie中存储重要和敏感的数据,它包含的任何数据都可以被他人访问。


猜你喜欢

转载自zhyp29.iteye.com/blog/2304280