专栏名称: 程序员的那些事
最有影响力的程序员自媒体,关注程序员相关话题:IT技术、IT职场、在线课程、学习资源等。
目录
相关文章推荐
码农翻身  ·  漫画 | 为什么程序员总是无法升职加薪? ·  昨天  
OSC开源社区  ·  四名CEO ,扬言要打造新一代基础软件技术栈 ·  4 天前  
OSC开源社区  ·  35岁草根程序员下桌,去另一个赛道写“Hel ... ·  2 天前  
51好读  ›  专栏  ›  程序员的那些事

第三方 JS 开发:前后端接口协议

程序员的那些事  · 公众号  · 程序员  · 2017-06-07 20:09

正文

|作者:  zmmbreeze 

|文章推荐阅读时间: 6分钟左右


在Web网页开发中有一个有意思的分支,既第三方Javascript脚本的开发。所谓第三方Javascript脚本,就是第三方服务商将自己的服务通过“HTML投放代码”的形式提供给网站使用。由于Javascript的动态特性,一般的第三方服务都会直接或间接的提供Javascript文件给网站页面加载。


这篇文章我们来着重讨论前后端接口协议。下图是一般的第三方Javascript脚本使用流程:



可以看到第三方Javascript是运行在开发者(即客户)所提供的网页中,而所在网页的域名往往和我们的接口服务器地址所在的域名不同。所以在设计前后端接口时需要使用支持跨域的前后端接口协议。


之所以出现跨域问题,是因为浏览器为了安全启用了一种同源策略(same-origin policy)。

在讨论同源策略的影响之前先要知道什么是『源』。从同源策略的英文same-origin policy,我们就已经知道源的英文origin。大家可以在浏览器的console里面运行一下console.log(location.origin)打印出当前网页的源是什么。就大概知道它其实是包含了网页地址的一些部分。


可以看到『源』其实包含了三个部分:协议、域名和端口。只要这三者不同就会受到同源策略的影响。IE是一个例外:端口号并未加入到同源策略的组成部分之中。所以http://example.com:8080/http://example.com属于同源并且不受任何限制。

再说回同源策略的影响:它会阻止网页上的Javascript向不同源的服务器发起XMLHttpRequest请求,如下简称XHR。除此之外还会阻止两个不同源的网页互相访问。这不是这篇文章要讨论的点,这里主要讨论的是网页Javascript与服务器之间的跨域请求。


为什么要阻止不同源的XHR请求呢?设想一下,你作为一个普通用户在浏览器里面刷着淘宝(https://taobao.com),这时候邮箱里面来了一封邮件。标题非常的吸引人,于是你不小心在同一个浏览器中点开了(https://attack.com)。这时候网页内包含的恶意代码就可以向淘宝的服务器去发起请求,获取你的隐私(例如商品浏览记录)。


因为你用了同一个浏览器,浏览器会记着你的登录session(即cookie)。幸好浏览器有同源策略,taobao.com和attack.com不是同源所以不能发起请求。试想一下如果没有同源策略,那网站将会是一个非常不安全的地方。

CORS

其实大部分的主流浏览器的XHR接口都已经支持了Cross-Origin Resource Sharing(CORS)。通过CORS浏览器便可以轻松实现跨域的请求了。例子如下:


你不需要担心安全问题,因为浏览器会判断请求返回的头部中是否包含Access-Control-Allow-Origin: http://example.com这样的头部信息。如果返回的Access-Control-Allow-Origin值包含当前网页的源,那么XHR才会拿到正确的返回值。而这个头部是否要返回完全是由服务器端来控制的。


这里面有两个细节:


  1. 如果XML发起的请求不是一个简单请求,那么浏览器会便会先发起一次 CORS 预检请求来检查自己是否有权限。所以为了避免多余的请求,一定要确保自己的请求是简单请求


  2. 如果想要在请求返回时由服务器种上第三方cookie,那么需要给XHR实例设置其withCredentials属性为true。在IE8-9中,微软给出过一个类似的标准实现:XDomainRequest接口(以下简称XDR)。这个接口虽然也能够实现跨域请求,但是它有一些限制。限制之一就是IE会剥离所有的cookie之后再发给服务器端,同时IE也会忽略所有的服务器端返回中的Set-Cookie指令。简而言之就是不支持cookie

从CORS的支持程度来说其实它已经几乎被所有的主流浏览器支持了,除了IE<10。随着浏览器的更新换代,相信不久之后CORS将会成为跨域请求的终极选择。不过目前为止,如果你的第三方Javascript还需要支持IE<10,那么你需要考虑一些其他方案。

JSONP协议

提到跨域请求一般大家先想到的是JSONP,其本质是利用浏览器可以加载不同域名下的Javascript文件。服务器端根据URL上的参数动态的生成不同的Javascript文件返回。这样浏览器端便可以接收到服务端的返回结果了。


例如一个典型的JSONP请求接口地址如下:

http://test.com/jsonp?callback=callback&id=0067ED


服务器端则会返回如下Javascript文件,其中包含了数据:


callback && callback({    id: '0067ED',    username: 'mmzhou'    // ......});


浏览器在接收到返回之后会自动运行这段JS,然后调用全局的callback方法。这个callback方法是由调用方的JS事先事先准备好的,这样来接收到返回的参数。通过JSONP协议服务器端可以传给浏览器大量的数据。通常请求下一般都会使用JSONP来实现拉取数据、获取配置等等才做。但有的时候我们仅仅需要向后端发送数据,并且不关心返回结果是什么,甚至不在乎有没有成功。

ping

这种只需要发送数据且不在乎返回结果的请求类型,我们在这篇文章中称之为ping。也有很多人喜欢称之为beacon。

小图片

ping协议其实是一种很古老的跨域方法。它的本质是利用了浏览器可以获取不同域名下图片的原理,把请求参数放在了图片地址的URL参数中发给后端。因为是图片的请求,所以网页上的JS是不能得到返回图片是什么样的。它的大致JS代码如下:


浏览器通过Image接口实例化之后的赋值其src属性为请求接口的URL地址,并把请求参数放在URL地址中。可以看到图片实例不需要插入到页面DOM树中,避免了返回图片展示出来被用户看到。


后端在接到请求之后返回204请求(表示执行成功,但是没有数据)或者是一张1x1像素的gif图片。204与gif图片的区别在于你是否想要知道请求响应是否成功。因为204返回会在部分浏览器下导致onerror的回调,这就会让JS分不清是请求发送失败还是成功。

页面关闭时发送数据

ping请求是很轻的,因为它只需要创建一个Image实例即可。但是它也有一个缺陷。我们先假设第三方Javascript需要实现一个功能:在页面关闭时发送请求给服务器端记录页面已经被关闭。一般的做法是在onload或者beforeOnload事件中发送ping请求。绝大多数浏览器会延迟卸载以保证图片的载入(数据发送成功),但并不是所有浏览器都是如此。而且浏览器会忽略在onload事件回调中产生的异步XHR请求 。所以要确保请求发送成功是很难的。


如果浏览器支持CORS,那么发送同步的XHR请求是可以 block 浏览器的UI主线程,同样也可以block浏览器关闭直到服务器端返回结果。但是如果服务器端响应慢,耗时超过250ms以上(普通人能够感知到了),这就会带来很差的用户体验。同时在浏览器UI主线程中的同步XHR请求已经在标准中被列为deprecated,之后浏览器将可能会不再支持(尽管这是一个漫长的过程)。


既然 block 住浏览器的UI主线程是可以延迟浏览器关闭的,那么可以想到另一个方法就是人为的设置一段时间的延迟。大概代码如下:


通过200ms的JS死循环来延迟浏览器关闭,为发送ping请求争取到更多的时间。200ms是一个很短的时间,用户一般不会察觉到。这其实在第三方Javascript开发中是一个常用的手段。

Navigator.sendBeacon

标准的制定者早已经看到了这个需求,所以已经提供了一个Navigator.sendBeacon接口来实现ping协议现在在做的事情。这个方法可以用来从浏览器向服务端异步地发送小的HTTP数据。它不像之前提到的两种方法,会延迟页面的onload影响下一个页面的加载。可惜的是至少目前为止(2017年5月8日)IE和Safari浏览器还没有支持它。



智能ping协议

无论是小图片方式的ping请求还是JSONP请求本质上还是是一个GET请求,它通过URL地址上的参数来传输数据。因为URL地址的长度虽然HTTP协议中没有明确说明,但是很多浏览器都有一个自己的上限。例如IE8的长度限制是2083。所以URL的长度最好不要太长。这就导致了一次JSONP的请求可以携带的请求参数量有限。这也是它们的最大缺点。


这点在XHR CORS和sendBeacon方法中是不存在的,因为它们可以发起POST请求,把请求参数放在HTTP body中。避免了数据传输量小的问题。


图片、XHR CORS和sendBeacon接口各有优缺点,如下:



我们可以根据传输数据在这三者之中智能地选择一种来传输。


普通情况下默认使用图片方式,因为它最轻量(GET方法)没有太多副作用。URL超过2083字符时优先使用navigator.sendBeacon方法,如果它不支持再使用XHR CORS方法。如果XHR CORS也不支持则再fallback到图片方式发送数据。能发则发部分浏览器会截断URL后发送。


如果是在onload事件回调中发送数据,则默认优先使用navigator.sendBeacon方法发送数据。不支持时再使用200ms延迟的小图片方式发送数据。

submit协议

之前已经提到过无论是小图片方式的ping协议还是JSONP协议,都存在一个发送数据量不能太大的问题。尤其是遇到图片上传之类的需求,使用它们是肯定做不到的。不过主流浏览器已经支持了FormData接口,有了它便可以通过XHR的方式发送文件了。例子如下:



虽然主流浏览器都支持了FormData接口,但是IE<=9仍旧不支持。为了解决这个问题我需要采用一些fallback手段。

form标签

HTML的form标签的提交是不受到同源策略限制的。换言之我们可以通过form标签来实现跨域请求。它的大致流程如下:


首先通过JS来创建一个同源的iframe标签,并在其内部创建一个form标签。然后把需要发送的数据项创建成一个个input标签添加到form标签内部。最后调用form标签的submit方法触发异步提交(当前页面不需要刷新)。


服务端接收到数据之后,把返回数据封装成到一张HTML网页中。这个网页会在iframe标签中执行展示,并将服务端返回的结果(例如JSON数据)通过跨域通讯的方式传给第三方JS所在的运行环境(即window.parent)。

这之中有几个细节:


  1. 同源的iframe标签的src属性必须是about: blank或者javascript: URL(即javascript伪协议),iframe标签内的文档可以继承原始文档的源。这样就不会因为同源策略而导致外部的第三方JS不能在iframe标签内添加form标签。同时也可以不用走网络请求去加载一个空白页面


  2. 之所以没有使用form标签的target属性来指向提交目标的iframe标签,是因为要减少对已有站点的影响


  3. 因为返回的HTML网页的域名是我们的接口域名,与当前网页不是同源,故会受到同源策略影响。需要用postMessage接口来实现跨域的通讯,这块在稍后的文章中会提到


  4. 请确保接口返回的Content-Type头部是text/html,不然部分浏览器可能会有不正确的行为


  5. form表单是可以上传文件的。在IE9+下因为一些特殊的安全策略,必须由用户交互(例如点击)才能触发文件选择框去选文件。不能通过input.click()这样的JS方法来触发。所以开发者需要创建一个用户可见的form表单去让用户选文件然后异步提交

在支持CORS和FormData接口的浏览器中,就可以优先XHR去提交数据。不支持的浏览器再fallback到form+iframe的方式异步提交。至此我们大概可以得到一个完整submit协议,调用方式如下:



总结

这篇文章中我们总结三种方式的前后端跨域接口协议:JSONP、ping以及submit协议。它们分别用于不同场景,主要的区别如下:



从表中可以看出来:JSONP协议主要适合于拉取数据、获取配置等不需要大数据量提交的操作;ping协议则更适用于不要关心返回结果的少数据量提交,尤其适合在页面关闭是发送数据;submit协议则适用于大数据量的提交,尤其适合文件上传;


作为一个第三方Javascript脚本的开发者,我们需要在不同的场景下选择最合适的接口协议,希望这篇文章讲述的技巧对你能有所帮助。


同时在文末,小编也要为对前端开发感兴趣的你,推荐一门由硅谷技术学习平台优达学城(Udacity)与 Google、GitHub、AT&T  Hack Reactor 的网页开发专家及招聘经理共同设计的认证项目,帮助学习者系统掌握前端开发技能,达到行业领导者认可的硅谷水平。


想成为前沿技术人才的你,加入课程后,将获得:


硅谷独家课程内容,Google、Github 开发课程并颁发认证

我们提供业内独家硅谷技术课程,课程及实战项目均与 Google、GitHub 等领先科技企业共同设计制作。此外我们还为学员提供一对一的在线学习辅导,学完课程即可获得名企颁发的学习认证,获得更多就业机会。


全中文的学习辅导

我们来自硅谷,但我们一直致力于让学员在本地进行更顺畅的学习。升级后的纳米学位项目,不仅拥有更完整优质的全套字幕,更为学员提供全中文的专业辅导,一样的硅谷标准,不一样的母语体验。


每周一次直播辅导,答疑没烦恼

除了学习来自硅谷领先企业的课程视频、实战项目,你还可以参与每周一次的专业直播讲解!还有很多 Udacity 独家学习资料,等待你来探索。


加入同步学习小组,在导师监督下加速成长

你将加入学习小组认识志同道合的小伙伴,在导师全方位的监督辅导下,用最高效率掌握前沿技能成为顶尖人才。


>> 课程报名倒计时中,点击[阅读原文],立即升级技能!


为了感谢大家长期以来对[程序员的那些事]的关注与支持,小编特意为大家申请了硅谷前沿技术学习平台 Udacity 前端开发课程的粉丝优惠码,在购买时输入我们的专属优惠码: "iprogrammer",即可立减 300元!只有关注了我们的你,才可以使用哦~