小宋爱睡觉 小宋爱睡觉
首页
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • 计算机网络
  • 浏览器原理
  • 性能优化
  • 设计模式
手写系列
  • 字符串
  • 数组
  • 链表
  • 树
  • 动态规划
  • 排序算法
  • GitHub (opens new window)
  • JueJin (opens new window)
首页
  • HTML
  • CSS
  • JavaScript
  • Vue
  • React
  • 计算机网络
  • 浏览器原理
  • 性能优化
  • 设计模式
手写系列
  • 字符串
  • 数组
  • 链表
  • 树
  • 动态规划
  • 排序算法
  • GitHub (opens new window)
  • JueJin (opens new window)
  • 网络攻击与防范
  • 进程和线程
  • 浏览器缓存
  • 浏览器的存储
  • 浏览器的渲染原理
  • 浏览器的同源策略
    • 介绍一下什么是同源策略
    • 如何解决跨域问题
    • 为什么会出现简单请求和非简单请求
    • 简述一下简单请求的过程
    • 简述一下非简单请求的过程
    • 其他跨域方式
      • JSONP
      • postMessage跨域
      • nginx代理跨域
      • nodejs中间件代理跨域
      • iframe跨域
      • WebSocket协议跨域
    • 介绍一下正向代理和反向代理
    • 负载平衡的两种实现方式
    • 四层负载均衡和七层负载均衡的区别
    • nginx概念及其工作原理
  • 浏览器的事件循环
  • 浏览器的垃圾回收机制
  • 浏览器组成
  • 浏览器原理
Crucials
2021-12-13
介绍一下什么是同源策略
如何解决跨域问题
为什么会出现简单请求和非简单请求
简述一下简单请求的过程
简述一下非简单请求的过程
其他跨域方式
JSONP
postMessage跨域
nginx代理跨域
nodejs中间件代理跨域
iframe跨域
WebSocket协议跨域
介绍一下正向代理和反向代理
负载平衡的两种实现方式
四层负载均衡和七层负载均衡的区别
nginx概念及其工作原理

浏览器的同源策略

# 浏览器的同源策略

# 介绍一下什么是同源策略特别重要

同源策略限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在恶意文件的重要的安全机制。同源指的是:协议、端口号、域名必须一致

同源策略主要限制了

  • 当前域下的js不能访问到其他域下的cookies、localstorage和indexDB

  • 当前域不能操作和访问其他域下的DOM

  • 当前域下的ajax无法发送跨域请求

同源策略主要是保证用户的信息安全,只是对js脚本的一种限制,并不是对浏览器的限制,对于一般的img都不会有跨域的限制,因为这些都不会出现安全问题

值得注意的是

<script>里面的src指向的请求链接也不会🈶️跨域的限制,参考JSONP的实现

# 如何解决跨域问题

  • CORS

概念

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain)上的Web应用被准许访问来自不同源服务器上的指定的资源

来自MDN (opens new window)

简单地来说就是只要服务器端实现了cors请求就可以跨源通信了

浏览器将cors分为简单请求和非简单请求

简单请求不会触发cors预检请求

HTTP请求方法是HEAD GET POST

HTTP请求头信息不超过以下的字段Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

值得注意的是

比较常见的json就不是简单请求了

head请求

请求资源的头部信息, 并且这些头部与HTTP GET 方法请求时返回的一致. HEAD 方法的响应不应包含响应正文.

通常用于

  • 下载一个大文件前先获取其大小再决定是否要下载, 以此可以节约带宽资源

  • 检查文件是否有最新版本,服务器会在响应头里把文件的修改时间传回来。

# 为什么会出现简单请求和非简单请求

跨域只是浏览器的一种保护机制,是为了保护站点的安全,其实跨域本身是不存在的,为了安全浏览器针对XMLHTTPRequest和fetch这种脚本发起的跨域请求做了一定的跨域限制,而对于浏览器自身的如img的src、script的js资源、form表单的提交,没有做跨域的限制。

简单请求:这是因为img的src、script的js只能发起get请求,而表单虽然能进行post提交,但一方面表单的提交是显式的,用户可以感知,另一方面,表单提交只能发起请求,而不能获取请求的响应,这样一来,请求可以发起,而服务端可以进行拒绝,浏览器认为这是安全的。

复杂请求:通过脚本发起的跨域请求,可以对响应内容做处理,这是用户不可感知的,浏览器认为这是不安全的,所以对于复杂请求,进行跨域的限制,而CORS机制就是浏览器对跨域进行处理。对于复杂请求会发起一个预检请求,判断服务端是否可以接受这个跨域请求,接受后就可以向服务端发起真正的请求。

# 简述一下简单请求的过程特别重要

浏览器会直接发出CORS请求,他会在他会在请求的头信息上增加一个Origin字段,该字段用来说明本次请求来自于哪一个源(协议+端口+域名),如果origin指定的域名在许可范围内,服务器返回的响应会多出以下信息

Access-Control-Allow-Origin: http://api.bob.com  // 和Orign一直
Access-Control-Allow-Credentials: true   // 表示是否允许发送Cookie
Access-Control-Expose-Headers: FooBar   // 指定返回其他字段的值
Content-Type: text/html; charset=utf-8   // 表示文档类型
1
2
3
4

如果指定的域名不在许可范围内,服务器会返回一个正常的HTTP回应,浏览器发现没有上面的Access-Control-Allow-Origin,表示就出错了,这个是无法通过状态码进行判断的

# 简述一下非简单请求的过程特别重要

非简单请求比如DELETE PUT会在正式通信之前会进行一次预检请求,就是当前所在的网页是否在服务器允许访问的范围内,以及可以使用的请求方法,只有得到服务端肯定的回复才能进行正式的HTTP请求,否则就会报错

预检的请求方法是option,他的头信息关键就是origin,除此之外还有

  • Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法。
  • Access-Control-Request-Headers: 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段

如果服务器根据头信息的三个字段进行判断,如果返回的头信息有Access-Control-Allow-Origin就是允许跨域请求

服务器返回的cors字段有

Access-Control-Allow-Origin: http://api.bob.com  // 允许跨域的源地址
Access-Control-Allow-Methods: GET, POST, PUT // 服务器支持的所有跨域请求的方法
Access-Control-Allow-Headers: X-Custom-Header  // 服务器支持的所有头信息字段
Access-Control-Allow-Credentials: true   // 表示是否允许发送Cookie
Access-Control-Max-Age: 1728000  // 用来指定本次预检请求的有效期,单位为秒
1
2
3
4
5

前三个是服务端是必须返回的字段

值得注意的是我们在日常的开发中要减少options请求次数,因为

  • 请求次数过多会损耗页面的加载性能,降低用户体验度
  • 可以在后端返回头部添加Access-Control-Max-Age表示可以被缓存多久,单位是秒,对完全一样的URL缓存设置生效,在这个时间段内就不需要预检了

还有一个是CORS中的cookies问题

在CORS请求中如果想传递cookies,要满足以下三个条件:

  • 在请求中加上withCredentials
// 原生 xml 的设置方式
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// axios 设置方式
axios.defaults.withCredentials = true;
1
2
3
4
5
  • Access-Control-Allow-Credentials 设置为 true
  • Access-Control-Allow-Origin 设置为非 *

# 其他跨域方式

# JSONP

原理

利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。

<script>
    var script = document.createElement('script');
    script.type = 'text/javascript';
    // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
    script.src = 'http://www.crucials:80/login?user=admin&callback=handleCallback';
    document.head.appendChild(script);
    // 回调执行函数
    function handleCallback(res) {
        alert(JSON.stringify(res));
    }
 </script>
1
2
3
4
5
6
7
8
9
10
11

服务端返回如下(返回时即执行全局函数)

handleCallback({"success": true, "user": "admin"})
1

Vue axios实现

this.$http = axios;
this.$http.jsonp('http://www.crucials:80/login', {
    params: {},
    jsonp: 'handleCallback'
}).then((res) => {
    console.log(res); 
})
1
2
3
4
5
6
7

nodejs代码

var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
server.on('request', function(req, res) {
    var params = querystring.parse(req.url.split('?')[1]);
    var fn = params.callback;
    // jsonp返回设置
    res.writeHead(200, { 'Content-Type': 'text/javascript' });
    res.write(fn + '(' + JSON.stringify(params) + ')');
    res.end();
});
server.listen('80');
console.log('Server is running at port 80...');
1
2
3
4
5
6
7
8
9
10
11
12
13

当然缺点就是仅支持get方法和不安全,可能遭到xss攻击

# postMessage跨域

主要解决了

  • 页面和其打开的新窗口的数据传递

  • 多窗口之间的消息传递

  • 页面与嵌套的iframe消息传递

用法:postMessage(data,origin)方法接受两个参数:

  • data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
  • origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

1)a.html:(a.com/a.html)

<iframe id="iframe" src="http://www.b.com/b.html" style="display:none;"></iframe>
<script>       
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: 'aym'
        };
        // 向b传送跨域数据
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.b.com');
    };
    // 接受b返回数据
    window.addEventListener('message', function(e) {
        alert('data from b ---> ' + e.data);
    }, false);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

2)b.html:(b.com/b.html)

<script>
    // 接收a的数据
    window.addEventListener('message', function(e) {
        alert('data from a ---> ' + e.data);
        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;
            // 处理后再发回a
            window.parent.postMessage(JSON.stringify(data), 'http://www.a.com');
        }
    }, false);
</script>
1
2
3
4
5
6
7
8
9
10
11
12

# nginx代理跨域

实际上与CORS跨域原理一样,都是配置Access-Control-Allow-Origin…等字段。

nginx配置解决iconfont跨域 浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置

location / {
  add_header Access-Control-Allow-Origin *;
}
1
2
3

nginx反向代理接口跨域 跨域问题

同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是使用HTTP协议,不需要同源策略,也就不存在跨域问题。

实现思路:通过Nginx配置一个代理服务器域名与a相同,端口不同)做跳板机,反向代理访问b接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域访问

#proxy服务器
server {
    listen       81;
    server_name  www.a.com;
    location / {
        proxy_pass   http://www.b.com:8080;  #反向代理
        proxy_cookie_domain www.b.com www.a.com; #修改cookie里域名
        index  index.html index.htm;
        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.a.com;  #当前端只跨域不带cookie时,可为*
        add_header Access-Control-Allow-Credentials true;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# nodejs中间件代理跨域

原理也与nginx大致相同,都是通过代理然后进行转发,也可以通过设置cookiesDomainRewrite参数修改响应头中cookies中域名,实现当前域的cookies写入,方便接口登陆认证

非vue框架的跨域 使用node + express + http-proxy-middleware搭建一个proxy服务器

前端:

var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问http-proxy-middleware代理服务器
xhr.open('get', 'http://www.a.com:3000/login?user=admin', true);
xhr.send();
1
2
3
4
5
6

中间件服务器代码

var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
app.use('/', proxy({
    // 代理跨域目标接口
    target: 'http://www.b.com:8080',
    changeOrigin: true,
    // 修改响应头信息,实现跨域并允许带cookie
    onProxyRes: function(proxyRes, req, res) {
        res.header('Access-Control-Allow-Origin', 'http://www.a.com');
        res.header('Access-Control-Allow-Credentials', 'true');
    },
    // 修改响应信息中的cookie域名
    cookieDomainRewrite: 'www.a.com'  // 可以为false,表示不修改
}));
app.listen(3000);
console.log('Proxy server is listen at port 3000...');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# iframe跨域

个感觉没那么重要

点击查看

# document.domain + iframe跨域

仅限主域相同,子域不同的跨域应用场景,实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域

1)父窗口:(domain.com/a.html)

<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
    document.domain = 'domain.com';
    var user = 'admin';
</script>
1
2
3
4
5

子窗口:(child.domain.com/a.html)

<script>
    document.domain = 'domain.com';
    // 获取父窗口中变量
    console.log('get js data from parent ---> ' + window.parent.user);
</script>
1
2
3
4
5

# location.hash + iframe跨域

实现原理:a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象

a.html:(www.a.com/a.html)

<iframe id="iframe" src="http://www.b.com/b.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');
    // 向b.html传hash值
    setTimeout(function() {
        iframe.src = iframe.src + '#user=admin';
    }, 1000);
    
    // 开放给同域c.html的回调方法
    function onCallback(res) {
        alert('data from c.html ---> ' + res);
    }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13

2)b.html:(www.b.com/b.html)

<iframe id="iframe" src="http://www.a.com/c.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');
    // 监听a.html传来的hash值,再传给c.html
    window.onhashchange = function () {
        iframe.src = iframe.src + location.hash;
    };
</script>
1
2
3
4
5
6
7
8

3)c.html:(www.a.com/c.html)

<script>
    // 监听b.html传来的hash值
    window.onhashchange = function () {
        // 再通过操作同域a.html的js回调,将结果传回
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
    };
</script>
1
2
3
4
5
6
7

# window.name + iframe跨域

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(2MB)

1)a.html:(a.com/a.html)

var proxy = function(url, callback) {
    var state = 0;
    var iframe = document.createElement('iframe');
    // 加载跨域页面
    iframe.src = url;
    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
    iframe.onload = function() {
        if (state === 1) {
            // 第2次onload(同域proxy页)成功后,读取同域window.name中数据
            callback(iframe.contentWindow.name);
            destoryFrame();
        } else if (state === 0) {
            // 第1次onload(跨域页)成功后,切换到同域代理页面
            iframe.contentWindow.location = 'http://www.a.com/proxy.html';
            state = 1;
        }
    };
    document.body.appendChild(iframe);
    // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
    function destoryFrame() {
        iframe.contentWindow.document.write('');
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
    }
};
// 请求跨域b页面数据
proxy('http://www.b.com/b.html', function(data){
    alert(data);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

2)proxy.html:(a.com/proxy.html)

中间代理页,与a.html同域,内容为空即可。

3)b.html:(b.com/b.html)

<script>    
    window.name = 'This is b data!';
</script>
1
2
3

通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作

# WebSocket协议跨域

这个感觉也不太重要的🦆

原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容

前端:

<div>user input:<input type="text"></div>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<script>
var socket = io('http://www.b.com:8080');
// 连接成功处理
socket.on('connect', function() {
    // 监听服务端消息
    socket.on('message', function(msg) {
        console.log('data from server: ---> ' + msg); 
    });
    // 监听服务端关闭
    socket.on('disconnect', function() { 
        console.log('Server socket has closed.'); 
    });
});
document.getElementsByTagName('input')[0].onblur = function() {
    socket.send(this.value);
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

node:

var http = require('http');
var socket = require('socket.io');
// 启http服务
var server = http.createServer(function(req, res) {
    res.writeHead(200, {
        'Content-type': 'text/html'
    });
    res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
// 监听socket连接
socket.listen(server).on('connection', function(client) {
    // 接收信息
    client.on('message', function(msg) {
        client.send('hello:' + msg);
        console.log('data from client: ---> ' + msg);
    });
    // 断开处理
    client.on('disconnect', function() {
        console.log('Client socket has closed.'); 
    });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 介绍一下正向代理和反向代理

  • 正向代理

    • 客户端想获得一个服务器的数据,但是因为种种原因无法直接获取。于是客户端设置了一个代理服务器,并且指定目标服务器,之后代理服务器向目标服务器转交请求并将获得的内容发送给客户端。这样本质上起到了对真实服务器隐藏真实客户端的目的。实现正向代理需要修改客户端,比如修改浏览器配置
  • 反向代理

    • 服务器为了能够将工作负载分配到多个服务器来提高网站性能 (负载均衡)等目的,当其受到请求后,会首先根据转发规则来确定请求应该被转发到哪个服务器上,然后将请求转发到对应的真实服务器上。这样本质上起到了对客户端隐藏真实服务器的作用。 一般使用反向代理后,需要通过修改 DNS 让域名解析到代理服务器 IP,这时浏览器无法察觉到真正服务器的存在,当然也就不需要修改配置了

img

区别:

正向代理和反向代理结构都是一样的,都是client-proxy-server的结构,他们主要的区别是在于中间的proxy是哪一方设置的,在正向代理中,proxy是client设置的,用来隐藏client,而在反向代理中,proxy是server设置的,用于隐藏server

# 负载平衡的两种实现方式

  • 一种是使用反向代理的方式,用户的请求都发送到反向代理服务上,然后由反向代理服务器来转发请求到真实的服务器上,以此来实现集群的负载平衡。
  • 另一种是 DNS 的方式,DNS 可以用于在冗余的服务器上实现负载平衡。因为现在一般的大型网站使用多台服务器提供服务,因此一个域名可能会对应多个服务器地址。当用户向网站域名请求的时候,DNS 服务器返回这个域名所对应的服务器 IP 地址的集合,但在每个回答中,会循环这些 IP 地址的顺序,用户一般会选择排在前面的地址发送请求。以此将用户的请求均衡的分配到各个不同的服务器上,这样来实现负载均衡。这种方式有一个缺点就是,由于 DNS 服务器中存在缓存,所以有可能一个服务器出现故障后,域名解析仍然返回的是那个 IP 地址,就会造成访问的问题。

# 四层负载均衡和七层负载均衡的区别

实现原理:四层基于IP+端口的方式进行路由转发,七层基于请求URL地址的方式进行代理转发。

实现方式:四层通过报文中的IP地址和端口,再加上负载均衡设备所采用的负载均衡算法,最终确定选择后端哪台下游服务器。

以TCP为例,客户端向负载均衡发送SYN请求建立第一次连接,通过配置的负载均衡算法选择一台后端服务器,并且将报文中的IP地址信息修改为后台服务器的IP地址信息,因此TCP三次握手连接是与后端服务器直接建立起来的。

七层服务均衡在应用层选择服务器,只能先与负载均衡设备进行TCP连接,然后负载均衡设备再与后端服务器建立另外一条TCP连接通道。因此,七层设备在网络性能损耗会更多一些。

安全方面:四层容易遭受SYN flood攻击,容易将垃圾流量转发到服务端,而七层会在负载均衡设备上进行过滤

四层负载均衡 七层负载均衡
实现原理 基于IP和端口 基于虚拟URL或主机IP
实现限制 类似路由器 代理服务器
握手次数 1 2
复杂度 低 高
性能 无需解析内容 需要算法识别URL,cookies,HTTP head等
安全性 低,容易被DDos攻击 可防御SYN Flood
额外功能 无 会话保持,图片压缩,防盗链

# nginx概念及其工作原理

Nginx 是一款轻量级的 Web 服务器,也可以用于反向代理、负载平衡和 HTTP 缓存等。Nginx 使用异步事件驱动的方法来处理请求,是一款面向性能设计的 HTTP 服务器。

传统的 Web 服务器如 Apache 是 process-based 模型的,而 Nginx 是基于event-driven模型的。正是这个主要的区别带给了 Nginx 在性能上的优势。

Nginx 架构的最顶层是一个 master process,这个 master process 用于产生其他的 worker process,这一点和Apache 非常像,但是 Nginx 的 worker process 可以同时处理大量的HTTP请求,而每个 Apache process 只能处理一个

上次更新: 2022/04/03, 23:38:00
浏览器的渲染原理
浏览器的事件循环

← 浏览器的渲染原理 浏览器的事件循环→

Copyright © 2021-2025 粤ICP备2021165371号
  • 跟随系统
  • 浅色模式
  • 深色模式