跨域问题
网络模型数据处理过程
域名的空间结构:
由于IP地址不方便记忆,所以同样用具有层次和唯一性的域名和IP一一映射DNS查找过程
- 客户端向本地域名服务器发出请求,请求www.baidu.com的IP地址
- 本地DNS服务器向DNS根服务器发出请求,根DNS服务器会告诉本地服务器(.com)的服务器地址
- 本地DNS服务器会向(.com域)发请求,会得到(baidu.com)的服务器地址
- 本地DNS服务器会向(baidu.com)发请求,会得到(www.baidu.com)的IP地址61.135.169.125
- 本地DNS服务器向客户端回复域名(www.baidu.com)对应的IP地址是61.135.169.125
同源策略和跨域
浏览器只对XHR(XMLHttpRequest)请求有同源请求限制,同源就是协议、域名和端口号一致,不同源的客户端脚本在没有明确授权的情况下,不能读写对方XHR资源,反之不同源脚本读取对方XHR资源就是跨域。以http://www.a.com/test/index.html 的同源检测举例:
- www.a.com/dir/page.ht… ----成功
- child.a.com/test/index.… ----失败,域名不同
- www.a.com/test/index.… ----失败,协议不同
- www.a.com:8080/test/index.… ----失败,端口号不同
跨域的解决方案
- jsonp:只支持GET,不支持POST请求,不安全XSS
- postMessage:配合使用iframe,需要兼容IE6、7、8、9
- document.domain:仅限于同一域名下的子域
- cors:需要后台配合进行相关的设置
- websocket:需要后台配合修改协议,不兼容,需要使用socket.io
- proxy:使用代理去避开跨域请求,需要修改nginx、apache等的配置
jsonp
- 浏览器对script标签src属性、link标签ref属性和img标签src属性没有同源策略限制,利用这个“漏洞”就可以很好的解决跨域请求,JSONP就是利用了script标签无同源限制的特点来实现的。
- 当向第三方站点请求时,我们可以将此请求放在script标签的src属性里,这就如同请求一个普通的JS脚本,可以自由的向不同的站点请求。
//创建script发送请求
//请求返回执行cb函数,并且删除创建的script
//类似于$ajax中的jsonp function jsonp(url,params,cb){ return new Promimse((resolve,reject)=>{
window[cb] = function(data){
resolve(data);
document.body.removeChild(script);
}
params={...params,cb}, let arrs=[]; for(let key in params){
arrs.push(`${key}=${params[key]}`)
} let script = document.createElement('script');
script.src= url + '?'+ arrs.join('&');
document.body.appendChild(script);
})
}
jsonp({
url:'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
params:{wd:%E8%B7%A8%E5%9F%9F},
cb:'show'}).then(data=>{
console.log(data)
}) 复制代码
- 输入https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=%E8%B7%A8%E5%9F%9F&&cb=show
- 返回show({q:"跨域",p:false,s:["跨域请求","跨域访问","跨域问题","跨越速运单号查询","跨域ajax","跨越物流","跨越","跨域速运","跨域请求解决方案","跨域问题怎么解决"]});
postMessage
配合iframes使用,假设a.html位于服务localhost:3000,b.html位于服务器localhost:4000
//a.html
<body>
<iframe id="frame" src="http://localhost:4000/b.html" frameborder="0" onload="load()"></iframe>
<script> function load(){ let frame = document.getElementById('frame');
frame.contentWindow.postMessage('我很帅','http://localhost:4000');
window.onmessage =function (e){
console.log(e.data);
}
}
</script>
</body>
//otherWindow.postMessage(message, targetOrigin);
//otherWindow:指目标窗口,也就是给哪个window发消息,是 window.frames 属性的成员或者由 window.open 方法创建的窗口
//message:是要发送的消息,类型为 String、Object (IE8、9 不支持)
//targetOrigin: 是限定消息接收范围,不限制请使用'*' //注意otherWindow和targetOrigin的区别 复制代码
//b.html
<body>
<script>
//data:消息
//origin:消息来源地址
//source:源DOMWindow 对象
window.onmessage =function (e){
console.log(e.data);
e.source.postMessage('不要脸',e.origin);
}
</script>
</body> 复制代码
document.domain
//a.html
<body>
helloa
<iframe id="frame" src="http://www.kongbz.com/b.html" frameborder="0" onload="load()"></iframe>
<script>
document.domain = 'kongbz.com';//设置domain function load(){ let frame = document.getElementById('frame');
console.log(frame.contentWindow.a)
}
</script>
</body> 复制代码
<body>
hellob
<script>
document.domain = 'kongbz.com';//设置domain
var a = 'isB' </script>
</body> 复制代码
websocket
客户端发送信息给服务端,如果想实现客户端向客户端通信,只能通过页面->服务端->另一个页面
//客户端
<body>
hellob
<script> let socket = new WebSocket('ws://localhost:3000');
socket.onopen = function(){
socket.send('我很帅')
}
socket.onmessage = function(e){
console.log(e.data)
}
</script>
</body> 复制代码
//服务端 let express = require('express'); let Websocket = require('wss'); let wss= new WebSocket.Server({port:3000})
wss.on('connection',function(ws){
ws.on('message',function(data){
console.log(data);
ws.send('不要脸');
})
}) let app = new express();
app.listen(3000) 复制代码
cors
const http = require('http')
const whitList = ['http://localhost:4000'];
http.createServer(function (req, res) { let origin = req.headers.origin;
//在白名单中的域名才能访问 if(whitList.includes(origin)){
//允许的域名(* 所有域),*不能和Access-Control-Allow-Credentials一起使用
res.header("Access-Control-Allow-Origin", "*");
//允许携带哪个头访问,不设置不能携带参数
res.header("Access-Control-Allow-Headers","ContentType");
//允许的方法,不设置默认支持GET、HEAD、POST,其他类型必须设置才能处理请求
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
//运行携带cookie,设置之后还能服务器才能接受cookie
res.header("Access-Control-Allow-Credentials",true);
//允许前端获取哪个头,不设置浏览器不能解析后台返回的参数
res.header("Access-Control-Allow-Expose-Headers",'ContentType'); if(req.method=== 'OPTIONS'){
res.end()
}
}
}).listen(9000, function () {
console.log('server is runing at 9000')
}) 复制代码
proxy
例如test.a.cn/index.html页面去调用test.b.cn/service.json
//nginx.conf
location / {
root;
index index.html index.htm;
}
location ~.*\.json {
root json;
add_header "Access-Control-Allow-Origin" "*";
} 复制代码
代理和网关
- 代理是一种有转发功能的应用程序,它扮演了位于服务器和客户端'中间人'的角色,接收客户端发送的请求不改变请求的URI并转发给服务器,同时也接收服务器返回的相应并转发客户端
- 缓存代理:代理转发响应时会预先将资源缓存在代理服务器上当代理再次接收到对相同资源的请求时,就可以不从源服务器那里获取资源,而是将之前缓存的资源作为响应返回
- 透明代理:转发请求或响应时,不对报文做任何加工的代理类型
- 网关是转发其他服务器通信数据的服务器,接收从客户端发送来的请求时,它就像自己拥有资源的源服务器一样对请求进行处理,其工作机制和代理类似,而网关能使通信线路上的服务器提供非HTTP协议的服务。
反向代理
- 大家都有过这样的经历,拨打10086客服电话,可能一个地区的10086客服有几个或者几十个,你永远都不需要关心在电话那头的是哪一个,叫什么,男的,还是女的,漂亮的还是帅气的,你都不关心,你关心的是你的问题能不能得到专业的解答,你只需要拨通了10086的总机号码,电话那头总会有人会回答你,只是有时慢有时快而已。那么这里的10086总机号码就是我们说的反向代理。客户不知道真正提供服务人的是谁。
- 反向代理隐藏了真实的服务端,当我们请求 www.baidu.com 的时候,就像拨打10086一样,背后可能有成千上万台服务器为我们服务,但具体是哪一台,你不知道,也不需要知道,你只需要知道反向代理服务器是谁就好了,www.baidu.com 就是我们的反向代理服务器,反向代理服务器会帮我们把请求转发到真实的服务器那里去。Nginx就是性能非常好的反向代理服务器,用来做负载均衡。
负载均衡和动静分离
- 负载均衡是反向代理的一种,后端多台服务器,nginx根据权重、压力、带宽的分配服务器,避免等待和拥塞
- 动静分离是反向代理的一种,后端服务器分为动态资源服务器和静态资源服务器,nginx会根据请求分配服务器,区分处理逻辑,加快响应
cookie、localstroage、sessionstroage的区别
-
Cookie适合存储一些session信息:
- cookie限制大小,约4k左右,不适合存储业务数据,尤其是数据量较大的值
- 存在有效期,到期自动销毁
- cookie会每次随http请求一起发送,浪费宽
- cookie设置了domain可以在子域共享跨域
- 可以使用爬虫抓取
-
localstroage适合存储应用共享的地址信息等:
- 存储数据量大,5M或者更大
- 有效期为永久
- 不会随http请求一起发送
- 不能跨域,但是可以使用postMessage和iframe消除这个影响,例如:cross-storage
- 在浏览器的隐私模式下不能读取
- 不能被爬虫读取
-
sessionstroage适合存储浏览状态等:
- 存储数据量大,5M或者更大
- 有效期为到浏览器关闭
- 不会随http请求一起发送
- 不能被爬虫读取