博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浏览器异步加载和同源策略
阅读量:6548 次
发布时间:2019-06-24

本文共 5269 字,大约阅读时间需要 17 分钟。

静态页面

在浏览器脚本的概念没有出现之前,所有的网页都是静态的。我们知道浏览器的工作模式是:

  1. 浏览器向网站服务器发起请求
  2. 网站接受浏览器的请求,返回一些字符串(比如一些组成页面的 HTML 字符串)
  3. 浏览器接收到网站返回的用于组成页面的字符串后,就可以关闭连接了
  4. 浏览器将组成页面的字符串渲染到屏幕上,使得用户可以看到一个可视化的结果

看起来就像下面这样:

                                        Client Request                                            +-------------+                                     +--------+       +------+      |  User Agent | +-------------------------------->  |        |       | User +------>             |                                     | Server |       +--^---+      |  (Browser)  | <--------------------------------+  |        |          |          +-------+-----+                                     +--------+          |                  |                Server Response                                |                  |                                                               |                  |                                                               |        +---------v--------+                                                      |        | Close Connection |                                                      |        +---------+--------+                                                      |                  |                                                               |                  |                                                               |         +--------v--------+                                                      ^---------+ Render response |                                                                +-----------------+                                                   复制代码

我们看到,一旦用户代理(浏览器)关闭了和服务器之间的链接之后,客户端和服务器之间将不能继续通信。

动态页面

为了让页面可以给用户带来更多的交互,浏览器开发厂商们制造出了名为浏览器脚本的东西。比如你在浏览一个页面的时候,你觉得页面的字体太小了。在静态页面的时候,页面制作者在右上角给你提供了名为 “放大字体” 的按钮,你点击那个按钮,然后开启一轮新的请求,显著的说就是说你感觉到浏览器刷新了。这其实是浏览器重新从服务器加载页面的资源,只不过这一次的资源是用于显示字体放大后的页面。(尽管这个例子现在看来有些奇怪,因为如果仅仅是改变页面字体大小的话,似乎直接操作 element.style.fontSize 就可以了,不过请求另外一个包含更大字体的内联 CSS 页面在最终效果上也是说得通的,所以这里还请多多包含了。)

浏览器脚本就是一小段由浏览器执行的代码,页面制作者将这一小段代码,和网页面的内容(比如一篇优美的散文,和它右上角的 “放大字体” 按钮)一起返回给浏览器。浏览器接收到页面资源后,首先就是先将散文和 “放大字体” 按钮显示出来。注意到返回的内容实际上还有一段由浏览器执行的代码,页面制作者在这段带代码中告诉浏览器:如果用户点击了 “放大字体” 按钮,那么你就将页面的字体放大。于是,当你点击 “放大字体” 按钮之后,浏览器严格执行页面制作者在脚本中撰写的内容 - 将页面的字体放大。

异步加载

注意在静态页面中浏览器和服务器之间的通信过程。浏览器在向服务器发起了对页面的请求之后,在服务器没有将页面的内容返回之前,页面是无法被显示出来的,最显著的特征就是我们在点击了浏览器的 “刷新” 按钮之后,页面会 “白屏” 一小段时间。

起初浏览器脚本是没有网络通信的功能的,只能做一些页面的特效,比如“点击按钮放大了字体”。不过浏览器厂商发现,如果给脚本赋予网络通信的功能,将使得页面制作者可以给用户提供更好的页面交互体验。于是在早期的 IE 浏览器中,首先赋予了浏览器脚本的通信功能。

浏览器脚本可以和服务器进行网络通信之后,页面制作者可以做出具有更好体验的页面。比如你现在需要搜索商品,假设是要买一本编程的书,你在网页的搜索框中输入了 “编程的数”,很明显是输错了,你将 “书” 错输成了 “数”。在你点击了 “搜索” 按钮之后,进过短暂的白屏之后,页面中显示了:

找不到关于 “编程的数” 的产品,你是不是要找 “编程的书”

很不错,网站给了我们一个提示,这样我们就可以发现自己的输入错误。不过这个体验还是有待提高的,因为每一次的搜索都会有一个短暂的 “白屏”,在白屏期间用户只能等待。在浏览器脚本可以通信之后,搜索就可以以一个异步的方式进行:

  1. 用户在浏览器中输入搜索页面的地址 “”
  2. 浏览器会向网站请求搜索页面的内容,用于显示这个页面
  3. 网站在返回页面的显示内容的同时,包含了一小段脚本,脚本的内容是告诉浏览器 “用户在点击了搜索之后,你给用户一个提示,让用户知道服务器正在紧张的搜索用户所需的资源,然后你显示了提示后,你再向服务器请求搜索的结果,当得到搜索结果后,你再把搜索结果显示给用户”

这样的话,用户不必在搜索时面对页面的刷新时的 “白屏” 了,有一个提示框告诉用户稍等片刻。

同源策略

为了定位网络上的资源,我们采用了统一资源定位符 URL,就像是一个门牌号一样, URL 标识出资源在网络上的位置。我们浏览的网页,其中的内容可能会来自不同的提供者,比如散文来自一位作家,而其中的配图来自一位美术家。散文的 URL 是 http://writer.com/new-world,配图的 URL 是 http://artist.com/new-world

我们需要有一种方式将网络上的资源(比如散文和图画)标识出来,区别它们是来自于不同的作者。如果我们将颗粒度定位到每一个独立的资源,理论上是可行的,但是我们知道作家不可能只有一篇散文,而美术家也不会只有一幅画。于是我们选择了使用:通信协议,完整的域名,以及端口号去描述一个源,只有三者都相同,才标识两个资源是同源的。

下面的几个资源是同源的:

http://example.com/ http://example.com:80/ http://example.com/path/file复制代码

下面的资源是不同源的:

http://example.com/ http://example.com:8080/ http://www.example.com/ https://example.com:80/ https://example.com/ http://example.org/ http://ietf.org/复制代码

现在知道了同源,那么同源策略是什么意思呢?同源策略就是,两个不同源的资源相互是不能访问对方的资源的。同源策略主要就是限制脚本的网络访问。

比如我们打开了一个页面 http://example.com,这个页面有两段脚本,一个段使用的内联的方式称为 A,它主要就是在用户点击了按钮之后显示一段文字,告诉用户点击了按钮;另一段作为外部资源进行加载称为 B,B 是 A 的基础代码,比如 B 是 jQuery,它被放在了 http://cdn.jquery.com 上。首先我们知道,这两段代码如果按照同源的定义,肯定是不同源的。也就是说我们在 http://example.com 的页面上是不能加载 http://cdn.jquery.com 上的资源的。

好像与现实情况有点矛盾。之所以现在可以,是因为浏览器为了适应实际的生产情况,放宽了对同源策略的检查,因为我们知道,不可能将所有的资源都放在同一台机器上。那么在页面完全加载好之后,页面中的脚本(内联的和外部引入)的都被浏览器归纳到了和当前页面相同的源,都属于 http://example.com 了。这么做的意思就是,脚本无法访问与之不同源的资源,也就是此时的脚本(内联的和外部引入的)无法访问资源 https://example.com/user-info

绕过同源策略

有时比如上面的例子,我们确实需要在脚本中加载和当前页面不同源的资源,比如在 http://example.com 页面中使用脚本加载 https://example.com/user-info 中的内容。那么如何绕过浏览器的同源策略呢?

我们知道直接在页面中载入不同源的外部资源是可以的,那么我们就可以动态的载入一段外部的脚本。

首先,我们的 http://example.com 中有这么一段脚本:

(function () {    window['showNickname'] = function (json) {        alert(json['nickname']);    };    var userInfoServiceUrl = 'https://example.com/user-info';    var doCrossSiteRequest = function (url, callback) {        var script = document.createElement('script');        script.src = url + '?callback=' + callback;        var head = document.getElementsByTagName('head');        if (head[0]) {            head.append(script);        }    };    document.querySelector('#btnShowNickName').addEventListener('click', function () {        doCrossSiteRequest(userInfoServiceUrl, 'showNickname');    });})();复制代码

https://example.com/user-info 的服务端内容为:

'net-user'];$json = json_encode($userInfo);echo "{
$callback}({
$json});";复制代码

那么在浏览器加载了 https://example.com/user-info 的脚本为,得到的是:

showNickname({
"nickname":"net-user"});复制代码

这就和我们最先在 http://example.com 留下的 window['showNickname'] 对接上了。

转载于:https://juejin.im/post/5c3ffb0251882523ea6df9e5

你可能感兴趣的文章
快速排序
查看>>
高清6分钟看完《指环王》三部曲
查看>>
NGINX 负载均衡 之九
查看>>
Ubuntu添加硬盘实验
查看>>
STP生成树协议的工作过程
查看>>
UI标签库专题十:JEECG智能开发平台 Form(form标签)
查看>>
spring mvc+mybatis整合cms内容发布平台
查看>>
vsftpd服务简介
查看>>
Maven 入门之单元测试
查看>>
MySql学习笔记(八):explain之extra
查看>>
一、Linux应用程序基础 1、应用程序与系统命令的关系 文件位置 主要用途...
查看>>
职场必备!学会这4个技巧,便可轻松操作Excel!
查看>>
红米手机3S 3X获取ROOT权限的经验
查看>>
年度必读:2018最具突破性人工智能论文Top 10
查看>>
如何操作PDF内容编辑
查看>>
.net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
查看>>
邮件安全与社会工程学
查看>>
Cheese : A tool to use your camera
查看>>
安装类问题
查看>>
js 查找文章中的关键字,并标红
查看>>