2016年4月21日星期四

阮一峰的网络日志

阮一峰的网络日志


个性也是一种竞争力

Posted: 20 Apr 2016 06:36 PM PDT

(说明: 本文原载2016年第15期《财新周刊》

1.

上个月发生了一件新闻,一个网名叫做"papi酱"的姑娘,得到了1200万元风险投资。

新闻稿这样说.

"真格基金、罗辑思维、光源资本和星图资本宣布对papi酱投资1200万元,占股12%;papi酱团队持股88%,估值1亿元。"

据报道,papi酱今年29岁,是中央戏剧学院导演系的在读研究生。她从2015年下半年开始,在网上发布各种二三分钟的短视频而走红。在视频里面,她以独白秀的形式,通过夸张的表情,以及变音器、对口型、方言等形式,大讲男女关系、社会现象等等。

光看视频的标题,就不难想象她谈论的内容:《当女人说"没事"的时候,到底在说些什么?》、《像个台湾人一样说东北话》、《美女的痛苦你们根本就不懂啊》......

"我们统计了一下主流平台的数据,papi 酱的视频总播放量超过 2.9 亿次,每集平均播放量 753 万。其中,点击最高的一集视频《有些人一谈恋爱就招人讨厌》,全网播放量达 2093 万次。这还只是 papi 酱个人账号的数据,不包括其他账号转发的情况。"

2.

我周围的朋友对这件事情很关注,倒不是因为爱好她的视频,而是很惊奇,难道现在风险投资的门槛已经这么低了?

以前,在我们的观念里面,你只有技术领先、做出了一个优秀产品,才能拿到风险投资。papi酱让大家发现,人人都可以获得风险投资,只要拿起手机,拍摄几分钟,上传到网上,有一大群粉丝追捧,就会有人愿意投资你。

这件事标志着,风险投资不再是技术创业者的专享了,人人都可以拿风投,人人都可以创业,只要你有足够的粉丝。

3.

技术的演进,似乎已经到了这样一个阶段:使用门槛极低,任何人都可以轻易掌握使用方法。

以前,你必须具备专门知识,才能使用技术工具;而现在,只要会用手机,就能生产出无穷无尽的内容。我有一个朋友,每天对着手机录音一分钟,有时读一段诗,有时说一些感想,发在微信上,也有上万个订阅者。

我们正在进入一个"后技术时代",特征就是技术无所不在,成为整个社会的基础设施,不再具有使用门槛,一个人就可以运作一家企业或者一家媒体。以前创业是比技术、比功能,今后创业可能就是比特色、比个性。

4.

手机行业就是技术泛化的一个例子。以前的手机行业,比的是谁的功能强,现在比的是各家的特色,比如配置、拍照、外观等等。因为如今的手机从外形到功能高度雷同,所有厂商都掌握基本技术,功能大家都一样。

今后,产品竞争的重点,很可能就不是技术了(因为大家的基本技术都差不多),而是产品的服务、外观设计、文化等非技术方面。其中很重要的一项,就是产品的个性。因为其他方面都容易复制,但个性最难复制。如果一个产品充斥着非常浓郁的个性,这样的产品最难被取代。

苹果公司就是一个例子,创始人乔布斯个性张扬,把控一切方面,从产品设计到软件功能,甚至苹果商店的柜台布置,无不体现他的个性和审美。结果使得苹果的产品有一种独特的人格和文化,吸引着全世界的消费者。可惜乔布斯死后,苹果的这种个性特色正在逐渐丧失。

papi酱也是这样的例子。她以前拍过写真,当过女主角,因为没有个性,结果都没走红。反而是素颜出镜,本色演出,对着摄像头恶搞,却开始全国出名。更极端的还有Lady Gaga,原来只是一个纽约酒吧的小歌手,就是因为穿着个性化的奇装异服,而变成世界巨星。

当个性变成了产品的一部分,就成了一种竞争力。以后,你需要两样东西,帮助你成功。一样是你的能力,还有一样是你的个性。它们互相配合,造就出独一无二的产品,令其难以被其他产品或技术取代。考虑到人的能力很难大幅超出其他人,那么个性的重要性就更加凸显。

5.

个性造就产品的独特性,在我们这个社会尤其如此。原因是中国社会强调集体和服从,不鼓励每个人发展自己的个性。所以,真正具有自己个性的人或产品,都不多。这种环境中,个性的超额回报就更高。这就好比,其他人都不会唱歌,只有你一个人会唱歌,即使歌声再差,听众也会趋之若鹜。

个性变成竞争力,还有一个原因是技术造成的。技术发展的一大结果就是差异消失。凡是技术主导的领域,生产出来的往往都是标准化产品。当你和其他人用的都是一样的产品时,你就开始怀念差异和个性了。举例来说,很多女孩子喜欢购买各种各样的手机套,如果没有这个套子,每个人的手机看上去就都一样了。总之,技术在消灭差异、拉近人们的生活方式和生活水平的同时,也在加大消费者对个性的重视。

papi酱获得投资,本质上就是个性商品化、个性企业化。这样的事情,今后将会越来越多。更深层次来说,技术是冰冷和没有人性的,技术越是主导人类社会的发展方向,就越需要人性作为平衡和纠正。技术正在很多方面胜过人类,而人性作为一种竞争优势,因而变得更明显了。

(完)

文档信息

2016年4月16日星期六

阮一峰的网络日志

阮一峰的网络日志


理查德·汉明《你和你的研究》

Posted: 15 Apr 2016 08:32 PM PDT

理查德·汉明(Richard Hamming,1915--1998)是著名美国计算机学家,图灵奖得主,"汉明码"的发明人。

1986年3月7日,他在贝尔通信研究中心给200多名科学家们,做了一次演讲,题目叫做《你和你的研究》(英文版中文版视频)。他试图回答下面这样一个问题。

"为什么有的科学家做出了影响深远的重大成果,而大多数其他人的成果都被历史遗忘了?"

这篇演讲非常有名,属于经典文献。不仅对科学工作者有启发意义,每一个从事创造性工作的人,都应该读一下。

下面是我的摘录,删除了一些涉及具体的人和事的段落,内容更集中易读,并且为每个部分加上了小标题。

=============================

你和你的研究(精简版)

You and Your Research

作者:理查德·汉明(Richard Hamming)

译者:未知

时间:1986年3月7日

我演讲的题目是"你和你的研究"。我要谈的不是普通的研究,而是重大的研究,就是相当诺贝尔奖那一级别的研究。比如,相对论,香农的信息论,以及其他杰出的理论----这就是我要讲的。

1945年,我进入洛斯阿拉莫斯国家实验室,负责计算机方面的事,因此见过费曼、费米、奥本海默等大科学家。我一直问自己这样的问题: "为什么他们做出了这样的成果?"和"我和他们的差别是什么" 。我读了传记、自传,去问他们本人:"你是怎么干起来这样的事的?" 我试着搞清原因,这就是今天要谈的内容。

为什么这个话题重要?因为你只有一次生命。即使你相信来世,那也没关系,为什么不在今生就做一些重大的事呢。

一、要有做大事的想法

首先,你要有干大事的想法。很多人毕生只是例行公事般地从事安全的工作,所以产出有限。就这么简单:如果你要干大事,你必须毫不迟疑地去解决重大难题。

我遇到费曼时,就知道他肯定能得诺贝尔奖。因为他会从事伟大的工作。不论未来走哪个方向,这个人都会干大事。

你要跟自己说,往前走,去做大事!

二、年轻的时候就要有勇气追求答案

成功科学家的重要品质之一就是勇气。

一旦你鼓起了勇气,相信自己能解决重要的问题,那么你就行。如果你觉得你不行,几乎肯定你不会去做。伟大的科学家都充满勇气,他们不管周围境况,勇往直前;他们思考、思考、再思考。

年龄是另一个因素。你要做就得趁年轻。爱因斯坦做事就早,所有的量子理论的大科学家开始做他们的"事"的时候,都早得吓人。大多数数学家、理论物理学家,以及天体物理学家都在他们的早年,作出了我们公认的他们最好的成就。这并不是说他们岁数大了以后,就不能做有益的工作。是我们认为他们最有价值的事是他们年轻的时候所为。另一方面,在音乐、政治和文学方面,通常的情况是,那些我们仰慕的大作品往往出炉较晚。

三、忍受不确定的状态

伟大科学家还有另一个性格方面的特点,那就是"似是而非"。大多数人愿意相信世上万物非此即彼,是非分明。大科学家们却能很大程度地容忍"似是而非"。他们对自己的想法非常自信,但是又保持足够的警觉,随时挑出其中的错误和瑕疵。

如果你过于自信,你将忽视其中的破绽;如果你过分怀疑,你甚至将无从起步。这需要一个良好的平衡。多数大科学家非常清楚为什么他们的理论是真知灼见,同时也知道哪里还有些小毛病。

四、比别人多努力10%

大多数伟大的科学家都有惊人的动力。他们总是比别人投入更多。假设两个人拥有几乎一样的能力,其中一个人比另一个人多干10%,几年后他将多产一倍。

你知道得越多,就学得越多;你学得越多,就做得越多;你做得越多,机会就越多。

五、全力投入重要的问题

如果你找到一件真正重要的事情,就不要让任何别的事情分散你的注意力。

多数大科学家口袋里约有一二十个大问题,想方设法去攻克。每当他们发现一个新想法出现的时候,你就会听到他们说:"唔,这个与该问题有关。" 他们于是抛开其他一切,全力投入此问题。

他们的思想是时刻准备着的,看见机会就紧跟其后。当然,很多时候也不能奏效,一个主要的诀窍就是活得长一点。

六、敞开办公室大门

另一个性格特点,我一开始没注意到:有人的办公室大门是关着的,另一些人是开着的。

我观察到,如果关上办公室的门,你今天或明天确实会比别人多干不少的活。但是,十年后就未必了。因为你可能干了不少不值得干的事。那些把门敞开的人,的确受了很多的打扰,但他也不时地获得线索,了解这世界什么更重要。

我可以说,那些敞开了门干活的人和最终成就了大事的人之间,存在千丝万缕的联系。

七、做可以成为他人基石的工作

早年,我在攻克一个又一个难题,成功的多,失败的少。可是,周五解决了一个问题回到家里后,我却并不快活,反而很沮丧。我看到生活就是一个问题接着一个问题又接着另一个问题。

想了相当长一阵子后,我决定以另一种方式干活:你的工作要成为别人工作的基石!于是别人就会说: "看哪,我站在他的肩膀之上,我看得更远了。"

科学的本质是积累!我再也不去做相互孤立的问题,除非它能代表某一类问题的共性。我决不再去解决单一的问题。

你要么让人们在你的成果上有所建树,要么别人不得不把你干的活从头再来复制一遍。

八、重视推销和表达自己

做完一件事情还不够,你还得把它"贩卖"出去。

对于一个科学家而言,推销是一件棘手的事。这很讨厌,你本不该做这事,这世界就该等着,当你做成某件大事时,他们就该赶快出来主动迎接。但是,事与愿违,每个人都忙着他们自己的活。你必须很好地主动介绍,使得他们能把手头的活放在一边,过来瞧瞧你的东西,理解它,然后回过头来说:"是,那玩意不错。" 现实是即使你发表了研究成果,如果读者们把你的文章翻过去,不停下来读它,你就会竹篮打水一场空。

你得学会写好、写清楚以便人们愿意看;你必须学会发表相当正式的发言;你还必须学会作出非正式的谈话。我们有不少所谓的"后排科学家"。在一个会议上,他们更愿意闭口不谈。他们不愿站在一个炙手可热的会议的中央,在大庭广众之下说:"我们应该做这件事,为了这些原因......" 你必须掌握这种形式的交流,准备发表演说。

我刚开始做演讲的时候,非常非常紧张,几乎表现出生理上的病态。我意识到,我得学习作演讲,否则整个职业生涯就不会有很好的发展。我决定一定要做一个非常好的演讲,一个真正符合听众需要的演讲,不只针对专业人士,而是针对更广泛的听对。

现在,我认为,研究要只花50%的时间,另外50%的时间用来表达,这是一个非常大的数字。

九、阅读方法比数量更重要

你也不要在图书馆花太多时间,一味追随别人的研究成果。

贝尔实验室有个同事,一个非常非常聪明的家伙。他老在图书馆里呆着,读所有的东西。如果你想要参考资料,你到他那里去,他就会告诉你所有的参考资料。但我觉得,长此以往,他不会有任何以他命名的成果。他现在已退休,成为了一个副教授。他是很有价值,我对此没有疑问。他写了一些不错的文章登在《物理评论》上,但他没有以他命名的成果,因为他读得太多。如果你成天研究别人怎么做,你就会按别人的老路子思考。

如果你想要有不同的新思维,你需要去搞清问题,而不是成天靠读书去找答案。这不是可取的方法。

我的建议是:阅读,但不是靠量,而是靠正确的阅读方法起作用。

十、重视写书

短期来看,论文非常重要。但如果你想要一个长期的认可,写书的作用更大,因为我们大多数人需要方向。现今的知识几乎是无穷的,我们需要寻找自己的方向。那些帮助我们融会贯通的、代表重要思想的书,会被后代珍视。

写论文也是必要的,但我倾向于认为,长期看,那些只写至关重要内容的书更重要。

十一、远见超出你的能力时,才去做管理

如果你想成为一名伟大的研究者,就不要成为一个公司的总裁。你得清楚你要什么。

一天,我到我的老板Bode那里,对他说:"为什么你要当这个部门的头呢?为什么你不去当一名大科学家呢?" 他说:"Hamming, 我有远见,知道贝尔实验室的数学部分要怎样,如果要让这个"远见"得到共识,我就得当上部门的头。"

当你觉得你想干什么的远见,正好在你的能力范围内,你就应努力获取它。如果有一天你的远见大大超过了你的能力时,你就应该去做管理工作。而且,远见越大,你就应做越大的管理。如果你拥有一个关于整个实验室应该向何处去,或者有关整个贝尔系统,你就得到该去的位子让它实现。你从底层是无法轻易让它实现的。

这取决于你的目标和对目标的渴望。我选择回避管理工作因为我更希望做我容易应付的事。但这是我的选择,只对我起作用。每个人有权做出自己的选择,保持一个开放的心态。但别试着两样都占。

(完)

文档信息

2016年4月12日星期二

阮一峰的网络日志

阮一峰的网络日志


跨域资源共享 CORS 详解

Posted: 11 Apr 2016 03:56 PM PDT

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。

它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

本文详细介绍CORS的内部机制。

(图片说明:摄于阿联酋艾因(Al Ain)的绿洲公园)

一、简介

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

二、两种请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两大条件,就属于简单请求。

(1) 请求方法是以下三种方法之一:

  • HEAD
  • GET
  • POST

(2)HTTP的头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

凡是不同时满足上面两个条件,就属于非简单请求。

浏览器对这两种请求的处理,是不一样的。

三、简单请求

3.1 基本流程

对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。

 GET /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... 

上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequestonerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

 Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8 

上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。

(1)Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

(2)Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true时,Cookie就会包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

(3)Access-Control-Expose-Headers

该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

3.2 withCredentials 属性

上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。

 Access-Control-Allow-Credentials: true 

另一方面,开发者必须在AJAX请求中打开withCredentials属性。

 var xhr = new XMLHttpRequest(); xhr.withCredentials = true; 

否则,即使服务器同意发送Cookie,浏览器也不会发送。

需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

四、非简单请求

4.1 预检请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

下面是一段浏览器的JavaScript脚本。

 var url = 'http://api.alice.com/cors'; var xhr = new XMLHttpRequest(); xhr.open('PUT', url, true); xhr.setRequestHeader('X-Custom-Header', 'value'); xhr.send(); 

上面代码中,HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header

浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。

 OPTIONS /cors HTTP/1.1 Origin: http://api.bob.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... 

"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

除了Origin字段,"预检"请求的头信息包括两个特殊字段。

(1)Access-Control-Request-Method

该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT

(2)Access-Control-Request-Headers

该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header

4.2 预检请求的回应

服务器收到"预检"请求以后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

 HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain 

上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。

 Access-Control-Allow-Origin: * 

如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。

 XMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin. 

服务器回应的其他CORS相关字段如下。

 Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Access-Control-Allow-Headers: true Access-Control-Max-Age: 1728000 

(1)Access-Control-Allow-Methods

该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

(2)Access-Control-Allow-Headers

如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。

(3)Access-Control-Allow-Credentials

该字段与简单请求时的含义相同。

(4)Access-Control-Max-Age

该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

4.3 浏览器的正常请求和回应

一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

下面是"预检"请求之后,浏览器的正常CORS请求。

 PUT /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com X-Custom-Header: value Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... 

上面头信息的Origin字段是浏览器自动添加的。

下面是服务器正常的回应。

 Access-Control-Allow-Origin: http://api.bob.com Content-Type: text/html; charset=utf-8 

上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。

五、与JSONP的比较

CORS与JSONP的使用目的相同,但是比JSONP更强大。

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

(完)

文档信息

2016年4月9日星期六

阮一峰的网络日志

阮一峰的网络日志


浏览器同源政策及其规避方法

Posted: 08 Apr 2016 07:02 AM PDT

浏览器安全的基石是"同源政策"(same-origin policy)。很多开发者都知道这一点,但了解得不全面。

本文详细介绍"同源政策"的各个方面,以及如何规避它。

一、概述

1.1 含义

1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。

最初,它的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。所谓"同源"指的是"三个相同"。

  • 协议相同
  • 域名相同
  • 端口相同

举例来说,http://www.example.com/dir/page.html这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略)。它的同源情况如下。

  • http://www.example.com/dir2/other.html:同源
  • http://example.com/dir/other.html:不同源(域名不同)
  • http://v2.www.example.com/dir/other.html:不同源(域名不同)
  • http://www.example.com:81/dir/other.html:不同源(端口不同)

1.2 目的

同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?

很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。

由此可见,"同源政策"是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。

1.3 限制范围

随着互联网的发展,"同源政策"越来越严格。目前,如果非同源,共有三种行为受到限制。

(1) Cookie、LocalStorage 和 IndexDB 无法读取。

(2) DOM 无法获得。

(3) AJAX 请求不能发送。

虽然这些限制是必要的,但是有时很不方便,合理的用途也受到影响。下面,我将详细介绍,如何规避上面三种限制。

二、Cookie

Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain共享 Cookie。

举例来说,A网页是http://w1.example.com/a.html,B网页是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享Cookie。

 document.domain = 'example.com'; 

现在,A网页通过脚本设置一个 Cookie。

 document.cookie = "test1=hello"; 

B网页就可以读到这个 Cookie。

 var allCookie = document.cookie; 

注意,这种方法只适用于 Cookie 和 iframe 窗口,LocalStorage 和 IndexDB 无法通过这种方法,规避同源政策,而要使用下文介绍的PostMessage API。

另外,服务器也可以在设置Cookie的时候,指定Cookie的所属域名为一级域名,这样二级域名和三级域名不用做任何设置,都可以读取这个Cookie。

三、iframe

如果两个网页不同源,就无法拿到对方的DOM。典型的例子是iframe窗口和window.open方法打开的窗口,它们与父窗口无法通信。

比如,父窗口运行下面的命令,如果iframe窗口不是同源,就会报错。

 document.getElementById("myIFrame").contentWindow.document // Uncaught DOMException: Blocked a frame from accessing a cross-origin frame. 

上面命令中,父窗口想获取子窗口的DOM,因为跨源导致报错。

反之亦然,子窗口获取主窗口的DOM也会报错。

 window.parent.document.body // 报错 

如果两个窗口一级域名相同,只是二级域名不同,那么设置上一节介绍的document.domain属性,就可以规避同源政策,拿到DOM。

对于完全不同源的网站,目前有三种方法,可以解决跨域窗口的通信问题。

  • 片段识别符(fragment identifier)
  • window.name
  • 跨文档通信API(Cross-document messaging)

3.1 片段识别符

片段标识符(fragment identifier)指的是,URL的#号后面的部分,比如http://example.com/x.html#fragment#fragment。如果只是改变片段标识符,页面不会重新刷新。

父窗口可以把信息,写入子窗口的片段标识符。

 var src = originURL + '#' + data; document.getElementById('myIFrame').src = src; 

子窗口通过监听hashchange事件得到通知。

 window.onhashchange = checkMessage;  function checkMessage() {   var message = window.location.hash;   // ... } 

同样的,子窗口也可以改变父窗口的片段标识符。

 parent.location.href= target + "#" + hash; 

3.2 window.name

浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。

父窗口先打开一个子窗口,载入一个不同源的网页,该网页将信息写入window.name属性。

 window.name = data; 

接着,子窗口跳回一个与主窗口同域的网址。

 location = 'http://parent.url.com/xxx.html'; 

然后,主窗口就可以读取子窗口的window.name了。

 var data = document.getElementById('myFrame').contentWindow.name; 

这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。

3.3 window.postMessage

上面两种方法都属于破解,HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。

这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。

举例来说,父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法就可以了。

 var popup = window.open('http://aaa.com', 'title'); popup.postMessage('Hello World!', 'http://aaa.com'); 

postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。

子窗口向父窗口发送消息的写法类似。

 window.opener.postMessage('Nice to see you', 'http://bbb.com'); 

父窗口和子窗口都可以通过message事件,监听对方的消息。

 window.addEventListener('message', function(e) {   console.log(e.data); },false); 

message事件的事件对象event,提供以下三个属性。

  • event.source:发送消息的窗口
  • event.origin: 消息发向的网址
  • event.data: 消息内容

下面的例子是,子窗口通过event.source属性引用父窗口,然后发送消息。

 window.addEventListener('message', receiveMessage); function receiveMessage(event) {   event.source.postMessage('Nice to see you!', '*'); } 

event.origin属性可以过滤不是发给本窗口的消息。

 window.addEventListener('message', receiveMessage); function receiveMessage(event) {   if (event.origin !== 'http://bbb.com') return;   if (event.data === 'Hello World') {       event.source.postMessage('Hello', event.origin);   } else {     console.log(event.data);   } } 

3.4 LocalStorage

通过window.postMessage,读写其他窗口的 LocalStorage 也成为了可能。

下面是一个例子,主窗口写入iframe子窗口的localStorage

 window.onmessage = function(e) {   if (e.origin !== 'http://bbb.com') {     return;   }   var payload = JSON.parse(e.data);   localStorage.setItem(payload.key, JSON.stringify(payload.data)); }; 

上面代码中,子窗口将父窗口发来的消息,写入自己的LocalStorage。

父窗口发送消息的代码如下。

 var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' }; win.postMessage(JSON.stringify({key: 'storage', data: obj}), 'http://bbb.com'); 

加强版的子窗口接收消息的代码如下。

 window.onmessage = function(e) {   if (e.origin !== 'http://bbb.com') return;   var payload = JSON.parse(e.data);   switch (payload.method) {     case 'set':       localStorage.setItem(payload.key, JSON.stringify(payload.data));       break;     case 'get':       var parent = window.parent;       var data = localStorage.getItem(payload.key);       parent.postMessage(data, 'http://aaa.com');       break;     case 'remove':       localStorage.removeItem(payload.key);       break;   } }; 

加强版的父窗口发送消息代码如下。

 var win = document.getElementsByTagName('iframe')[0].contentWindow; var obj = { name: 'Jack' }; // 存入对象 win.postMessage(JSON.stringify({key: 'storage', method: 'set', data: obj}), 'http://bbb.com'); // 读取对象 win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*"); window.onmessage = function(e) {   if (e.origin != 'http://aaa.com') return;   // "Jack"   console.log(JSON.parse(e.data).name); }; 

四、AJAX

同源政策规定,AJAX请求只能发给同源的网址,否则就报错。

除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。

  • JSONP
  • WebSocket
  • CORS

4.1 JSONP

JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器不用做任何改造。

它的基本思想是,网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

首先,网页动态插入<script>元素,由它向跨源网址发出请求。

 function addScriptTag(src) {   var script = document.createElement('script');   script.setAttribute("type","text/javascript");   script.src = src;   document.body.appendChild(script); }  window.onload = function () {   addScriptTag('http://example.com/ip?callback=foo'); }  function foo(data) {   console.log('Your public IP address is: ' + data.ip); }; 

上面代码通过动态添加<script>元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。

服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。

 foo({   "ip": "8.8.8.8" }); 

由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。

4.2 WebSocket

WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

下面是一个例子,浏览器发出的WebSocket请求的头信息(摘自维基百科)。

 GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com 

上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。

正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

 HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat 

4.3 CORS

CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。

下一篇文章,我会详细介绍,如何通过CORS完成跨源AJAX请求。

(完)

文档信息