一.WebSocket
WebSocket心跳重连是为了保证链接的可持续性和稳定性。在使用原生的WebSocket的时候,如果设备网络断开,不会触发WebSocket任何事件函数,前端程序无法得知当前链接已经断开。这个时候如果调用WebSocket.send方法,浏览器会发现消息发不出去,便会立即或者一定时间后(不同的浏览器或者浏览器版本不同可能表现不同)触发onclose函数。
后端webSocket服务也可能出现异常,连接断开后前端也并没有接收到消息,因此需要前端定时发送心跳消息,后端接受类似的消息立马返回,告知连接正常。如果一定时间没有收到消息,就说明连接异常,前端需要重新连接。
为了解决上述问题,以前端为主动方,定时发送给消息,用来检测网络和前后端连接问题。一旦发生异常,前端持续执行重新连接逻辑,直到连接成功。
let socket; // websocket实例
let lockReconnet = false // 避免重新连接
//新建websocket的函数,页面初始化,断开连接时重新调用
const getwebsocket = () =>{
let wsUrl = 'ws://ip'; // url网址
socket = new WebSocket(wsUrl)
//绑定一些事件,onerror,onclose, onopen,onmessage
//如果希望websocket连接一直保持,可以在close或者error上绑定重连方法
// 这样一般正常情况下失去连接时,触发onclose方法,就能重连了
socket.onclose = function(event) {
console.log("websocket服务出错了')
reconnect(wsUrl)
}
socket.onclose = function(event) {
console.log('websocket服务关闭了')
reconnect(wsUrl)
}
socket.open = function(event) {
heartCheck.reset().start() // 传递信息
}
socket.onmessafe = function (event)=>{
// 如果获取到消息,心跳检测重置
// 拿到任何消息都说明当前连接是正常的
console.log('websocket服务获得数据了')
// 接受消息后的UI变化
doWithMsg(event.data)
heartCheck.reset().start()
}
// 收到消息推送
function doWithMsg(msg){
// 执行业务代码
}
}
// 重新连接
const reconnect = (url) => {
if(lockReconnect) return
lockReconnect = true
// 没连上会一直重连,设置延迟避免请求过多
setTimeout(function() {
getwebsocket()
lockReconnect = false
},2000)
}
// 心跳检测
let hearcheck = {
timeout: 60000, // 60秒
timeoutObj: null,
serverTimeoutObj: null,
reset: () => {
clearTimeout(this.timeoutObj)
clearTimeout(this.serverTimeoutObj)
return this
},
start: ()=>{
let _this = this;
this.timeoutObj = setTimeout(()=>{
// 这里发送一个心跳,后端收到后,返回一个心跳消息
// onmessage拿到返回的心跳就说明连接正常
socket.send('心跳测试')
// 如果超过一定的额事件还没重置,说明后端主动断开了
_this.serverTimeoutObj = setTimeout(function(){
// 这里再onclose里执行了reconnect
// 所以这里检测到超时之后,执行socket.close()就触发了onclose,进而执行了reconnect
socket.close()
}, _this.timeout)
},this.timeout)
}
}
什么条件下执行心跳?
当onopen连接上时,我们便开始执行。如果在定时时间范围内,onmessage获取到了后端的消息,就重新执行。如果距离上次从后端获取到的消息超过60秒后执行心跳检测,判断下是否断联了,检测时间可以根据时间情况设定。
二.Token一般是存放在哪里?
一般用户通过用户名和密码登录成功后,服务器将登录凭据做数字签名,加密之后得到的字符串作为token。
它在用户登录成功后会返回给客户端,客户端主要有这么几种存储方式:
1.存储在localStorage中,每次调用接口的时候都把它当作一个字段传给后台。
2.存储在cookie中,让它自动发送,不过缺点是不能跨域。
3.拿到之后存储在localStorage中,每次调用接口的时候放在HTTP请求头的Authorization字段里,token在客户端一般存放在local Storage,cookie或者sessionStorage中。
三. 在什么情况下会触发options请求?
1.options的作用:用于获取目的资源所支持的通信选项。客户端可以对特定的URL使用options方法,也可以
对整站(通过将URL设置为*)使用该方法。
换言之:可以使用options请求去嗅探某个请求在对应的服务器中都支持哪种请求方法。在跨域的情况下,浏
览器发起复杂请求前会主动发起options请求。跨域共享标准规范要求,对那些可能对服务器数据产生副作用
的http请求方法(特别是get以外的http请求,或者搭配某些MIME类型的POST请求),浏览器必须首先使用
options方法发起一个预见请求,从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的
http请求。
2.不会触发cors预检请求一般为简单请求,触发预检请求称为复杂请求。
3.简单请求类别:
满足以下条件的请求为简单请求
(1 请求方法:get post head
(2 除了以下请求头字段之外,没有自定义的请求头:
Accept
Accent-Language
Content-Language
Content-Type
DPR
Downlink
Save-Data
Viewport-Width
Width
(3 Content-Type的值仅限于下列三者之一:
text/plan
multipart/form-data
application/x-www-urlencoded
(4 请求中的任意的XMLHttpRequestUpload对象均没有注册任何事件监听器;XMLHttpRequestUpload对象
可以使用XMLHttpRequest.upload属性访问。
(5 请求中没有使用ReadableStream对象。
4.复杂请求类别:
即非简单请求就是复杂请求。复杂请求我们也可以称之为实际进行请求前,需要发起预检请求的请求。
需预检的请求必要首先使用options方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。
预检请求的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
跨域请求时,options请求触发的条件:
(1 使用了下面任意http方法:put delete connect options trace patch
(2 人为设置了以下集合之外的首部字段: Accept Accept-Language Content-Language Content-Type
DPR Downlink Save-Data Viewport-Wdith Width
(3 Content-Type:的值不属于下列之一:application/x-www-form-urlencoded multipart/form-data
text/plain
四.对服务端渲染SSR了解多少?
1.概念:
服务端渲染是数据与模板组成的html,即html = 数据 + 模板。将数组或页面通过服务器生成html字符串,再发送
到 浏览器,最后将静态标记混合为客户端上完全交互的应用程序。页面没有使用服务渲染,当请求页面时,返
回的body里为空,之后执行js将html结构注入到body里,结合css显示出来。
2.优势:
a.所有的模板,图片等资源都存在服务器端;
b.一个html返回所有的数据;
c.减少http请求;
d.响应快,用户体验好,受屏渲染快。
e.非常利于SEO:
seo不同于爬虫工作原理,只会爬取源码,不会执行网站的任何脚本(google除外,Google可以运行js)。使
用React或者其他MVVM框架之后,页面大多数DOM元素都实在客户端根据js动态生成,可供爬虫抓取分析的内容
大大减少。另外,浏览器爬虫不会等待我们的数据抓取完成后再抓取页面数据。服务端渲染返回给客户端的是已
经获取了异步数据并执行js脚本的最终html,网络爬虫就可以抓取到更完整的页面信息。
f.更利于首屏渲染:
首屏的渲染时node发送过来的html字符串,并不依赖于js文件了,使用客户就会更快地看到页面的内容,尤其
是针对大型单页面应用,打包后文件体积比较大,普通客户端渲染加载所有所需文件时间较长,首页就会
有一个很长的白屏等待时间。
3.劣势:
a.服务端压力较大:
本来时通过客户端完成渲染的,现在统一到服务端node服务去做。尤其时高并发访问的情况,会大量占用服务
端CPU资源。
b.开发条件受限:
在服务端渲染中,只会执行到componentDidMount之前的生命周期钩子,因此项目引用的第三方的库也不可
用其他生命周期,这对引用库的选择产生了很大的限制。
c.学习成本相对较高
除了对webpack,MVVM框架要熟悉,还需要掌握node,koa2等相关技术。相对于客户端渲染,项目构建,部署
过程更为复杂。
4.耗时比较:
a.数据请求
由服务端请求首屏数据,而不是客户端请求首屏数据,比较快。服务端在内网进行请求,数据响应速度快。
客户端在不同网络环境进行数据请求,并且外网http请求开销大,导致时间差大。
b.html渲染
服务端渲染时先向后端服务器请求数据,然后生成完整首屏html返回给浏览器;客户端渲染是等js代码
下载,加载,解析完成之后在请求数据渲染,等待的过程页面是什么都没有的,就是用户所看到的白屏。
服务端渲染不需要等待js代码下载完成并请求数据,就可以返回一个已有完整的数据首屏页面。
五.说一下什么是运营商劫持?有什么预防措施?
运营商是指那些提供宽带服务的ISP,包括三大运营商中国电信,中国移动,中国联通,还有一些小运营商,比如
长城宽带,歌华有线等。运营商提供最基础的网络服务,掌握着通往用户物理大门的钥匙。
运营商劫持常见的有两种:DNS劫持和HTTTP劫持。
DNS(Domain Name System),域名系统,可将域名和IP地址相互映射的一个分布式数据库。
1.DNS劫持有三种情况:
a.错误域名解析到纠错导航页面,导航页面存在广告。判断方法:访问的域名是错误的,而且跳转的导航页也
是官方是我,譬如电信的114,联通移网域名纠错导航页面。
b.错误域名解析到非正常页面,对错误的域名解析到导航页的基础上,有一定几率解析到一些恶意站点,这些
恶意站点通过判断你访问的目标HOST,URI,referrer等来确定是否跳转广告页面,这种情况就有可能导致跳
转广告页面(域名输错)或者访问页面被加广告(页面加载时有些元素的域名错误而触发),这种劫持会对用
户访问的目标HOST,URI,referrrer等会进行判定来确定是否解析恶意站点地址,不易被发现。
c.直接将特点站点解析到恶意或者广告页面,这种情况比较恶劣,而且出现这种情况未必就是运营商所为,家里
路由器被黑或者系统入侵,甚至运营商的某些节点被第三方恶意控制都有可能。
2.HTTP劫持:
DNS解析的域名ip地址不变,但是在和网站交互的过程中劫持了请求。
目前运营商劫持率是3%~25%,防劫持措施如下:
1.全站HTTPS,能防一部分;
2.加入防运营劫持代码,能防大部分注入型劫持;
3.记录log,记录证据,向工信部投诉。
<meta charset="utf-8">
<title>防劫持代码测试</title>
<script>
console.log("发生劫持,初始化就直接置顶的流氓行为,暂无法防御,但可以通过埋点记录LOG,向工信部投诉");
</script>
<!--防劫持-->
<script shendun-eddy>
(function(){
var srcFilterTags = ["script","iframe"];
var whiteList = ["zeptojs.com"];
var whiteListReg = [];
whiteList.forEach(function(wl){
if(addedNode.src==="" && addedNode.getAttribute("shendun-eddy") !== null){
return true;
}
var isInWhiteList = false;
whiteListReg.forEach(function(wReg){
if(wlReg.test(addedNode.src)){
isInWhiteList = true;
return false;
}
});
return isInWhilte;
}
var mutationHandler = function(records){
records.forEach(function(record){
Array.prototype.slice.call(record.addedNodes).forEach(function(addedNode){
srcFilterTags.forEach(function(tagName){
if(addedNode.tagName === tagName.toUpperCase() && !inWhileList(addedNode)){
addedName.remove();
}
});
});
});
}
var MutationObserver = window.MutationObserver
|| window.WebKitMutationObserver
|| window.MozMutationObserver;
observeMutationSupport = !!MutationObserver;
var html = document.getElementsByTagName("html")[0];
if(observerMutationSupport){
new MutationObserver(mutationHAndler).observe(html,{
childList: true,
subtree: true
});
}
var eleList = document.querySelectAll("script");
var len = eleList.length;
for(var i = 0; i < len; i++){
if(!inWhileList(eleList[i])){
eleList[i].remove();
}
}
})();
</script>
<script>
console.log("劫持");
</script>
</head>
</body>
<script src="https://mt.cnzz.com/js/hdpi_canvas.js"></script>
<script src="http://zeptojs.com/zepto.min.js"></script>
<script src="http://yun.dui88.com/qiho-h5/jqg/shendun_test.js"></script>
<script> console.log("劫持…."); </script> </body> </html> ```