前端基础-HTTP/HTML/浏览器(2)

记录一些重要的知识点

流程

tcp三次握手,一句话概括

tcp三次握手

  • 三次握手可以简化为:C发起 请求连接SYN,S确认 发送给确认响应ACK+请求连接SYN,C确认发回ACk。双工连接建立成功
    • syn、ack/syn、ack
  • 每次握手的作用:
    1. 第一次握手:客户端向服务端发送 SYN 报文,表示请求建立一个从客户端到服务端的连接
    2. 第二次握手:服务端收到 SYN 报文后,根据确认机制,如果向客户端回复一个 ACK 报文,则表示同意建立连接,这样客户端通过syn建立好了客户端到服务器的连接,此时服务器如果也想建立到客户端的连接,咋整?简单,服务器也发送syn到客户端,所以最后实际发的是SYN/ACK 报文
    3. 第三次握手:客户端收到 SYN/ACK 报文后,向服务端发送一个 ACK 报文,表示服务端到客户端的连接已经建立,那么服务端就可通过连接发送数据给客户端了

tcp握手为什么是三次不是四次

  • 总结:因为tcp的连接是双工的,即双方都可以同时发送和接收数据,因此在建立连接时需要进行双向确认,所以是三次,如果采用四次握手,会增加连接的建立时间和资源开销,因此 TCP 协议采用三次握手来尽可能地减少连接的建立时间和网络资源的占用
    • 补充:http协议是单工的,所以http请求的数据传输是单向的。websocket是建立在tcp协议上的,所以是全双工通信,服务端可以主动向客户端推送数据。

为什么tcp挥手可以是三次/四次

  • 四次的情况:
    • 首先fin是用于关闭从客户端到服务器的连接表示客户端没有数据给服务器了
    • 服务器收到该fin后,会回复ack,这是TCP确认机制的要求。这时候,客户端是不能发送数据给服务器,但服务器仍然可以发数据给客户端
    • 等服务器没数据给客户端时,也发送给fin给客户端,关闭从服务器到客户端的连接
    • 客户端收到对端的fin后,回复ack
  • 到这里,经过四次挥手,双工的TCP连接才完全关闭
  • 三次的情况:
    • 客户端要关闭到服务器连接发送fin给到服务器时,刚好服务器也没数据给客户端,服务器就回复fin/ack了
    • 这样,四次挥手变成三次啦!
    • 这也好理解,你没话跟我说(没数据给我),我既可以有话跟你讲(有数据给你),也可以对你无言(无数据给你),选择权在我(回复ack还是fin/ack,我说了算)
  • 综上,握手一定是三次,挥手既可以是三次也可以是四次,这两种情况都能保证关闭连接后对方的数据已经接收完成从而不会导致数据的丢失,当然一般都是说四次挥手

tcp三次握手和tcp协议和http协议的关系

  • 一定要注意区分,TCP 三次握手 和 TCP 协议 是 HTTP 协议 能够建立可靠的数据传输通道的前提条件。在 HTTP 协议中,数据的传输使用了 TCP 协议提供的可靠的数据传输服务
  • TCP 三次握手建立 TCP 连接的过程,它在 TCP 协议层面进行。在客户端发送 HTTP 请求之前,它需要先与服务器建立 TCP 连接。TCP 三次握手的过程是客户端和服务器之间进行的,用于建立 TCP 连接并确认连接可用性。只有在建立好 TCP 连接后,客户端和服务器之间才能进行数据的传输,才能发送和接收 HTTP 请求和响应
  • TCP 协议是一种传输层协议,属于全双工协议,它支持双向通信。用于在网络中传输数据。TCP 提供了可靠的、面向连接的数据传输服务,确保数据能够按照一定顺序和完整性传输。在 TCP 中,数据被分成一个个数据包进行传输,每个数据包都包含了源 IP 地址、目标 IP 地址、源端口号、目标端口号、序号、确认号、窗口大小等信息,用于保证数据传输的可靠性
  • HTTP 协议是一种应用层协议,属于单工协议,用于在 Web 中传输数据。HTTP 协议建立在 TCP 协议之上,使用 TCP 提供的可靠的数据传输服务。当客户端需要从服务器获取数据时,它会发送一个 HTTP 请求,服务器会将请求的数据打包成 TCP 数据包进行传输。当数据传输完毕后,服务器会返回一个 HTTP 响应,响应数据也会被打包成 TCP 数据包进行传输

TCP和UDP的区别

TCP UDP
面向连接可靠性传输(三次握手) 无连接,即发送数据前不需要先建立链接。
提供可靠的服务。通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达
(因为tcp可靠,面向连接,不会丢失数据因此适合大数据量的交换。)
尽最大努力交付,即不保证可靠交付
面向字节流 面向报文,且网络出现拥塞不会使得发送速率降低(因此会出现丢包,对实时的应用比如IP电话和视频会议等)
TCP只能是1对1 UDP 支持1对1,1对多
TCP的首部较大,为20字节 UDP只有8字节

一个图片url访问后直接下载怎样实现

  • 重点:<a>的download 属性定义了下载链接的地址
  • 设置请求的返回头(HTTP响应报文首部的content-type),用于浏览器解析的重要参数就是OSS的API文档里面的返回http头,决定用户下载行为的参数。

使用HTML5实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
downloadIamge(imgsrc, name) {//下载图片地址和图片名  
let image = new Image();
// 解决跨域 Canvas 污染问题(允许跨域)
image.setAttribute("crossOrigin", "anonymous");
// onload 事件在图片加载完成后立即执行
image.onload = function() {
let canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
let context = canvas.getContext("2d"); //创建canvas对象
context.drawImage(image, 0, 0, image.width, image.height);
let url = canvas.toDataURL("image/png"); //得到图片的base64编码数据
let a = document.createElement("a"); // 生成一个a元素
let event = new MouseEvent("click"); // 创建一个单击事件
a.download = name || "photo"; // 设置图片名称
a.href = url; // 将生成的URL设置为a.href属性
a.dispatchEvent(event); // 触发a的单击事件
};
image.src = imgsrc;
},

地址栏输入URL到页面呈现的过程

这是一个必考的面试问题,输入url后,

  1. 客户端 建立连接 发送请求:
    • DNS解析:
      • 首先需要找到这个url域名的服务器ip,为了寻找这个ip,先搜索浏览器自身的DNS缓存,如果存在,则域名解析到此完成。
      • 如果浏览器自身的缓存里面没有找到对应的条目,那么会尝试读取操作系统的hosts文件看是否存在对应的映射关系,如果存在,则域名解析到此完成。
      • 如果本地hosts文件不存在映射关系,则查找本地DNS服务器(ISP服务器,或者自己手动设置的DNS服务器),如果存在,域名到此解析完成。
      • 如果本地DNS服务器还没找到的话,它就会向根服务器发出请求,进行递归查询
    • 建立TCP连接(三次握手):
      • 客户机发送一个TCP连接请求报文-》服务器回送一个TCP确认响应报文-》客户机向服务器发送一个 “TCP确认”的报文
    • 发送http请求
      • 浏览器根据这个ip以及相应的端口号,构造一个http请求,这个请求报文会包括这次请求的信息,主要是请求方法,请求说明和请求附带的数据,并将这个http请求封装在一个tcp包中,这个tcp包会依次经过传输层,网络层,数据链路层,物理层到达服务器。(ISO 模型中的第五个层次应用层就是http请求这一层,就不会经过了)
  2. 服务端 处理请求 发出响应:
    • 过三次握手建立了可靠的tcp连接后就可以进行是数据传输了。服务器收到 TCP 数据包后,会将 TCP 数据包中的 HTTP 请求报文解析出来,并发送 HTTP 响应给客户端,即返回相应的html给浏览器。
  3. 客户端 解析数据 渲染页面 断开连接:
    • 构建DOM树:浏览器根据这个html来构建DOM树,在dom树的构建过程中如果遇到JS脚本和外部JS连接,则会停止构建DOM树和css解析(因为JS有可能修改DOM结构,或者通过改变html元素的字体大小改变css的rem)来执行和下载相应的代码,这会造成阻塞,这就是为什么推荐JS代码应该放在html代码的后面
      • js的解析涉及同步、异步(宏任务与微任务)事件循环
    • 构建CSS对象模型树CSSOM树:解析html过程中遇到引入了css,则会在解析html的同时解析css,根据外部样式,内部样式,内联样式构建一个CSS对象模型树CSSOM树
    • 合并为渲染树: CSSOM树构建完成后CSSOM树和DOM树合并为渲染树,这里主要做的是排除非视觉节点,比如script,meta标签和排除display为none的节点。
    • 最后浏览器回流后将渲染树绘制到屏幕上显示。这个过程比较复杂,涉及到两个概念: reflow(回流)和repain(重绘)
      • 回流会引起元素位置/内容变化、布局、隐藏的就会**reflow(回流)**,如:窗口大小改变、字体大小改变、以及元素位置改变,都会引起周围的元素改变他们以前的位置。
      • 重绘:不会引起位置、布局变化,只是影响元素的外观的,比如改变背景颜色等,只会**repaint(重绘)**。
    • 注意:在dom树的构建过程/渲染树的渲染过程中如果遇到JS脚本和外部JS连接,则会停止渲染(因为JS有可能修改DOM结构,或者通过改变html元素的字体大小改变css的rem)来执行和下载相应的代码,这会造成阻塞,这就是为什么推荐JS代码应该放在html代码的后面
      • 总结代码放置顺序:CSS HTML JS
  • 另外的HTTP请求: html文件中会含有 图片、视频、音频、js、jquery、css等其他资源,这些又是另外的HTTP请求。在解析DOM的过程中,遇到这些都会进行并行下载,浏览器对每个域的并行下载数量有一定的限制,一般是4-6个,可能会通过一个网页产生很多的HTTP请求,请求越少性能越好
  • 为提高性能要关注缓存(强缓存和协商缓存),缓存一般通过Cache-Control、Last-Modify、Expires等首部字段控制。
    • Cache-Control和Expires的区别在于Cache-Control使用相对时间,Expires使用的是基于服务器 端的绝对时间,因为存在时差问题,一般采用Cache-Control。
    • 在请求这些有设置了缓存的数据时,如是强缓存,则不发送请求,直接从缓存中获取数据
    • 如是协商缓存,则会发送请求到服务器,如果上一次 响应设置了ETag值,则会在这次请求的时候作为If-None-Match的值交给服务器校验,如果一致,继续校验客户端发送的IF-Modified-Since与服务端的 Last-Modified是否一致,没有设置ETag则直接验证Last-Modified,都一致则返回304(Not Changed)告知浏览器可以直接从缓存获取,否则返回最新的资源内容
  • 连接结束:
    • 浏览器请求的资源已经被服务器完全发送给浏览器时,浏览器会进行 TCP 四次挥手,释放 TCP 连接
      1. 客户端向服务器发送 FIN 报文,表示客户端不再发送数据。
      2. 服务器收到 FIN 报文,向客户端发送 ACK 报文,表示服务器已经接收到客户端的 FIN 报文。
      3. 服务器向客户端发送 FIN 报文,表示服务器不再发送数据。
      4. 客户端收到 FIN 报文,向服务器发送 ACK 报文,表示客户端已经接收到服务器的 FIN 报文。

cookie相关

  • cookie有哪些编码方式:encodeURI()
  • 补充说明一下cookie的作用
    • 保存用户登录状态。例如将用户id存储于一个cookie内,这样当用户下次访问该页面时就不需要重新登录了,现在很多论坛和社区都提供这样的功能。
    • cookie还可以设置过期时间,当超过时间期限后,cookie就会自动消失。因此,系统往往可以提示用户保持登录状态的时间:常见选项有一个月、三个 月、一年等。
    • 跟踪用户行为。例如一个天气预报网站,能够根据用户选择的地区显示当地的天气情况。如果每次都需要选择所在地是烦琐的,当利用了cookie后就会显得很人性化了,系统能够记住上一次访问的地区,当下次再打开该页面时,它就会自动显示上次用户所在地区的天气情况。因为一切都是在后 台完成,所以这样的页面就像为某个用户所定制的一样,使用起来非常方便定制页面。
    • 如果网站提供了换肤或更换布局的功能,那么可以使用cookie来记录用户的选项,例如:背景色、分辨率等。当用户下次访问时,仍然可以保存上一次访问的界面风格

cookie有哪些字段可以设置

  • 例子
  • name字段:一个cookie的名称
  • value字段:一个cookie的值
  • Size字段:此cookie大小
  • domain字段:可以访问此cookie的域名域名图示
    • 非顶级域名,如二级域名或者三级域名,设置的cookie的domain 只能为顶级域名或者二级域名或者三级域名本身,不能设置其他二级域名的cookie,否则cookie无法生成。
      • 二级域名能读取设置了domain为顶级域名或者自身的cookie,不能读取其他二级域名domain的cookie。所以要想cookie在多个二级域名中共享,需要设置domain为顶级域名,这样就可以在所有二级域名里面或者到这个cookie的值了。
    • 顶级域名:只能设置domain为顶级域名,不能设置为二级域名或者三级域名,否则cookie无法生成。
      • 顶级域名只能获取到domain设置为顶级域名的cookie,其他domain设置为二级域名的无法获取。
  • path字段:可以访问此cookie的页面路径。比如domain是abc.com,path是/test,那么只有/test路径下的页面可以读取此cookie。
  • expires/Max-Age 字段:此cookie超时时间。若设置其值为一个时间,那么当到达此时间后,此cookie失效。不设置的话默认值是Session,意思是cookie会和session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie失效。
    • 注意:其实在浏览器关闭后,session并没有失效,正常来说一个session的存活时间是30分钟,也就是在不操作这个session的情况下,30分钟自动清除。可是实际中浏览器关闭,设置的session也会随之消失,这里的消失是指你找不到这个session,不是他没有了,因为在浏览器重新打开时,浏览器总会自动给你创建一个的新的session。session并不是唯一的,每个session都有自己的一个专属sessionId,这个sessionId在浏览器打开时创建,保存在浏览器的cookie中,浏览器关闭,cookie自动清除,sessionId丢失,之前的session找寻不到
  • http字段:cookie的httponly属性。若此属性为true,则只有在http请求头中会带有此cookie的信息,而不能通过document.cookie来访问此cookie。(即禁止javascript脚本来访问cookie)
  • secure 字段:设置是否仅在请求为https的时候传递此条cookie

cookie如何防范XSS攻击

  • XSS(跨站脚本攻击)是指攻击者在返回的HTML中嵌入javascript脚本
  • HTTP头部配上HttpOnly(后端通过set-cookie:httponly,前端通过document.cookie = "cookieName=cookieValue; HttpOnly";这样设置),禁止javascript脚本来访问cookie。严格来说,HttpOnly 并非阻止 XSS 攻击,而是能阻止 XSS 攻击后的 Cookie 劫持攻击。(在node中的设置方法可参考博客项目登录(cookie)
  • 设置cookie的secure属性为secure:true,告诉浏览器仅在请求为https的时候发送cookie

XSS和csrf网络攻击

  • 区别: XSS是利用用户对指定网站的信任,CSRF是利用网站对用户的信任。

XSS与防范

  • XSS跨站脚本攻击,攻击者通过注入恶意的脚本,在用户浏览网页的时候进行攻击,比如获取cookie,或者其他用户身份信息。
    • 存储性(持久型):用户输入的带有恶意脚本的数据存储在服务器端。当浏览器请求数据时,服务器返回脚本并执行。主要是通过html标签注入,篡改网页,插入恶意的脚本,前端可能没有经过严格的校验直接就进到数据库,数据库又通过前端程序又回显到浏览器。
      • 比如:攻击者在社区或论坛上写下一篇包含恶意 Js代码的文章或评论,文章或评论发表后,所有访问该文章或评论的用户,都会在他们的浏览器中执行这段恶意的 JavaScript 代码。
    • 反射型(非持久型):把用户输入的数据”反射”给浏览器。通常是,用户点击链接或提交表单时,攻击者向用户访问的网站注入恶意脚本。
      • 比如:在正常页面上添加一个恶意链接。恶意链接的地址指向localhost:8080。然后攻击者有一个node服务来处理对localhost:8080的请求:当用户点击恶意链接时,页面跳转到攻击者预先准备的localhost:8080页面,执行了 恶意的js 脚本,产生攻击。
  • XSS预防 : 前端在显示时替换,后端在存储时替换,都做总不会有错(可参考XSS网络攻击及防范)
    1. 替换特殊字符,如<变为&lt; >变为&gt,<script>变为&lt;script&gt;直接显示,而不会作为脚本执行(实际工作中可使用XSS工具)
      • 例子:前端在显示时替换特殊字符显示效果特殊字符在html中会被浏览器解析显示为相应的符号,故script以字符串形式显示,不会作为脚本执行
    2. HTTP头部配上HttpOnly(后端通过set-cookie:httponly,前端通过document.cookie = "cookieName=cookieValue; HttpOnly";这样设置),禁止javascript脚本来访问cookie。严格来说,HttpOnly 并非阻止 XSS 攻击,而是能阻止 XSS 攻击后的 Cookie 劫持攻击。(在node中的设置方法可参考博客项目登录(cookie)
    3. 设置cookie的secure属性为secure:true,告诉浏览器仅在请求为https的时候发送cookie
  • XSS工具:替换特殊字符可使用XSS工具
    • node使用XSS的方法可参考博客:博客项目安全
    • 前端使用XSS的方法可参考官方例子:参考官方例子

CSRF与防范

  • CSRF跨站请求伪造,攻击者借助用户的 Cookie 骗取服务器的信任,以用户名义伪造请求发送给服务器。如:在请求的url后加入一些恶意的参数。换句话说,CSRF就是利用用户的登录态发起恶意请求
  • 比如:你登录网站,并在本地存下了cookie,如果在没退出该网站的时候不小心访问了恶意网站,而且这个网站需要你发一些请求等,此时,你是携带cookie进行访问的,那么你的存在cookie里的信息就会被恶意网站捕捉到,那么你的信息就被盗用,导致一些不法分子做一些事情。
  • 假设某银行网站A以GET请求来发起转账操作,转账的地址为www.xxx.com/transfer.do?accountNum=l000l&money=10000,参数accountNum表示转账的账户,参数money表示转账金额。而某大型论坛B上,一个恶意用户上传了一张图片,而图片的地址栏中填的并不是图片的地址,而是前而所说的砖账地址:<img src="http://www.xxx.com/transfer.do?accountNum=l000l&money=10000">
  • 当你登录网站A后,没有及时登出,这时你访问了论坛B,不幸的事情发生了,你会发现你的账号里面少了10000块…(上述只是举例,转账怎么可能是get请求,为了保险,肯定是post请求,更何况银行的交易付款会有登录密码和支付密码等一系列屏障,流程复杂得多,安全系数也高得多)
  • 防御
    1. 使用token验证:在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,若请求无 token 或者 token 不正确,则认为可能是 CSRF 攻击而拒绝该请求。
      • 每次请求时 CSRF Token 都是不同的,一般由后端生成后set-cookie设置到cookie中,前端就会在请求时通过cookie带上。(例子)
      • 例子:
        • 比如有个网站,点赞是 get 方式请求:http://www.xxxx.com/like?id=12365
        • 如果没有验证 token 的话,我只要随便在某个网页发个图,图片地址设置这个 url,对方只要访问这个页面,加载这个图,对方就会自动给 id 为 12365 的帖子点赞了。
        • 如果验证 token,点赞的 get 的可能就是这种:http://www.xxxx.com/like?id=12365&token=A23F267AE65,由于每个人的 token 是变化的,你无法预先知道对方的 token,对方加载了 http://www.xxxx.com/like?id=12365 ,也不会成功
    2. 检查https头部的Referer:在HTTP头中有一个字段叫做Referer,它记录了该HTTP请求的来源地址。通过Referer Check,可以检查是否来自合法的”源”。
      • 例如:从www.user.com发起的删帖请求,那么Referer值是http://www.user.com, 删帖请求应该被允许;而如果是从CSRF攻击者构造的页面www.attack.com发起删帖请求, 那么Referer值是http://www.attack.com, 删帖请求应该被阻止。
    3. 验证码:验证码会强制用户必须与应用进行交互,才能完成最终请求,但是也不能给网站所有的操作都加上验证码,所以只能作为防御 CSRF 的一种辅助手段,而不能作为最终的解决方案。
    4. 使用post接口:img这些跨域只能接收get请求,设计post请求的需要server端的允许

cookie之外的存储方式

还有localStorage,sessionStorage,indexdDB等


cookie、sessionStorage、localStorage区别

  • 共同点:都保存在浏览器端
  • 区别:
  1. 生命周期
    • Cookie:设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。未设置则默认为关闭浏览器后失效。(不是浏览器标签页,而是整个浏览器)
    • Localstorage:除非被手动清除,否则永久保存,窗口或浏览器关闭也一直保存,因此用作持久数据
    • Sessionstorage:仅在当前网页会话下有效,关闭页面或浏览器后就会被清除(刷新页面不会被清除),自然也就不可能持久保持。
  2. 存放数据
    • Cookie:4k左右(cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下)
    • Localstorage和sessionstorage:可以保存5M的信息
  3. 是否跟随http请求发送出去
    • Cookie:每次都会携带在http头中,如果使用cookie保存过多数据会带来性能问题
    • 其他两个:不会自动把数据发给服务器,仅在客户端即浏览器中保存,不参与和服务器的通信
  4. API易用性
  5. 应用场景
    • 从安全性来说,因为每次http请求都回携带cookie信息,这样子浪费了带宽,所以cookie应该尽可能的少用,此外cookie还需要指定作用域,不可以跨域调用,限制很多,但是对于用户识别用户登陆来说,cookie还是比storage好用
      • cookie检查登录状态:
        • 前提须知:cookie会通过每个请求携带传输给后端,后端set-cookie设置的cookie在后续同源请求中会自动携带
        • 登录后,后端将sessionId作为uId传给前端cookie中,每次请求中(包括isLogin请求),后端都可以通过cookie中携带的uId到session中读取相关数据判断登录与否决定返回数据/是否处理请求
        • cookie有效期也由后端set-cookie控制,前端进入每个页面前通过isLogin接口来判断登录状态(该接口不需要另外传参,后端通过cookie就处理了)
        • 进入每个页面之前,前端可调用isLogin接口判断登录与否(vue的route.js中设置了app.router.beforeEach做处理,除了登录页其他都先走isLogin接口判断,无登录则跳转login页)
    • 其他情况下可以用storage,localstorage可以用来在页面传递参数
    • sessionstorage可以用来保存一些临时的数据,防止用户刷新页面后丢失了一些参数。
  6. 作用域:
    • cookie和localStorage:在同一个浏览器窗口所有同源窗口中都是共享的
    • sessionStorage:在同一个浏览器窗口是共享的(不同浏览器、同一个页面也是不共享的)
    • 关于同源可参考阮一峰,简单来说就是A网页设置的 Cookie,B网页不能打开,除非这两个网页”同源”。所谓**”同源”指的是”三个相同”**。
      1. 协议相同
      2. 域名相同
      3. 端口相同
    • 举例来说,http://www.example.com/dir/page.html这个网址,协议http://域名www.example.com端口80(默认端口可以省略)。它的同源情况如下。
      1. http://www.example.com/dir2/other.html:同源
      2. http://example.com/dir/other.html:不同源(域名不同)
      3. http://v2.www.example.com/dir/other.html:不同源(域名不同)
      4. http://www.example.com:81/dir/other.html:不同源(端口不同)
    • 每个浏览器都会维护自己的存储空间,不会共享

cookie和session的区别

  1. cookie数据存放在客户的浏览器上,**session数据放在服务器**上。(session可以存放于文件,数据库,内存中)
  2. cookie不是很安全,server端可以修改cookie并返回给浏览器,浏览器中也可以通过javascript修改cookie(有限制)。别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。server端放置信息不会被前端获取到,更安全。且server端空间大,可存储更多信息
  3. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用redis。
  4. 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
  5. Cookie的最大的作用就是存储sessionId(或者userId、uId之类的,名字不一样,作用都一样)用来唯一标识用户。也可存放不敏感的信息,比如用户设置的网站主题、地区

node操作cookie

  • 可参考博客项目登录(cookie)
  • 注意:当浏览器收到带有 Set-Cookie 头的 HTTP 响应时,它会将该 Cookie 存储在浏览器中。然后,对于同一域名和路径的后续请求,浏览器会自动在请求头中添加相应的 Cookie 信息
    • 前提:响应中的 Set-Cookie 头设置了有效的 Cookie 值和属性,例如名称、值、过期时间、路径等。
    • 一般会存一个uid:账号id,通过这个识别不同的用户发出的请求
  • 获取cookie:可通过req.headers.cookie获取到cookie (req是http请求)
  • 操作cookie:
    1
    res.setHeader(`Set-Cookie`,`username=${data.username}; path=/`);
  • 设置httponly预防XSS攻击
    1
    res.setHeader(`Set-Cookie`,`httpOnly`);
  • 设置cookie过期时间完整代码在“博客项目登录(cookie)”中
    1. const d = new Date();
    2. d.setTime(d.getTime()+(24*60*60*1000));getTime()setTime()
    3. d.toUTCString()根据格林威治时间 (GMT) 把 Date 对象转换为字符串
    4. 1
      res.setHeader(`Set-Cookie`,`expires=${d.toUTCString()}`);

node操作session

  • 可参考博客项目登录(session、redis)
  • 总的来说,从req.headers.cookie获取cookie后放入req.cookie,如无userId则按日期的毫秒数(Date.now())和随机数(Math.random())组成的userId放入cookie(注意Set-Cookie回给客户端),创建全局变量SESSION_DATA,创建空对象SESSION_DATA[userId]={},最后把req.session=SESSION_DATA[userId]。接下来登录后把username等信息放入req.session中即存储成功。
  • koa2操作session:
    • 完整例子可参考redis、session与jest基本使用方法
    • 与node的区别:
      • 使用插件后就不需要自己去创建cookie和session以及设置他们之间的联系了,插件配置好session和cookie相关信息后,每次发送http请求时会自动创建符合要求的cookie给服务端,服务端直接设置ctx.session来存储存储用户名和密码等信息即可。下次用户再访问网页时,服务端就可通过用户请求中的cookie值去获取session对象,进行判断(服务端不用自己管理cookie,直接用session即可)
    • 安装koa-generic-session插件:koa 生成session的一个工具
      • session配置中可以设置cookie相关字段,配置好后,每次用户发送http请求时,都会创建一个符合配置关键词前缀的cookie,根据用户请求中的cookie获取session数据
    • 使用插件完成session配置后,可以从**ctx.session获取session**
    • 流程:用户每次访问网页时就会创建一个 符合session配置中前缀的cookie(cookie对象的属性值是session对象的对象名),然后服务端会有一系列操作往session中存储用户名和密码等,那么下次用户再访问网页时,服务端就可通过用户请求中的cookie值去获取session对象,进行判断

前端性能优化

怎么看网站的性能如何

检测页面加载时间一般有两种方式

  1. 一种是被动去测:就是在被检测的页面置入脚本或探针,当用户访问网页时,探针自动采集数据并传回数据库进行分析
  2. 另一种主动监测的方式,即主动的搭建分布式受控环境,模拟用户发起页面访问请求,主动采集性能数据并分析,在检测的精准度上,专业的第三方工具效果更佳,比如说性能极客

前端优化

  • 可参考JS 运行环境
  • 加快请求速度web worker多线程,react中异步组件(懒加载)
    1. 减少资源体积:压缩代码
      • 比如使用webpack在production环境下进行打包时,就会自动压缩代码至1/3的大小,在浏览器上进行反解析再进行渲染。
    2. 减少访问次数
      • 雪碧图
      • 合并代码:比如webpack中,在index.js中引入a.js、b.js,打包后只生成一个bundle.js,这就是合并代码。(加载3次3kb的文件不如加载1次9kb快,这和网络请求有关系
      • SSR服务器端渲染:不需要通过ajax发送请求。
      • 缓存:原本需要发送多个请求的数据直接在缓存中获取即可减少访问次数。
    3. 使用更快的网络:CDN
      • CDN是分区域的,也就是使用CDN的时候上海和北京对同一个网站的IP地址是不同的。CDN会选择一个离用户最近的CDN边缘节点来响应用户的请求,这样海南移动用户的请求就不会千里迢迢跑到北京电信机房的服务器(假设源站部署在北京电信机房)上了
      • 图片、js等静态资源采用CDN是很快的,我们经常使用的bootstrap就是使用的CDN
  • 渲染
    • CSS放在head中,JS放在body最下面
    • 尽早开始执行JS,用DOMContentLoaded触发
    • **懒加载**(图片懒加载,上滑加载更多)
    • DOM查询进行缓存(DOM操作很耗性能)
    • **频繁DOM操作,合并**到一起插入DOM结构
    • 让渲染更流畅:**节流throttle 防抖debounce**
  • 减少回流、重绘:
    • **合并DOM操作**:比如我们要向页面添加多个img元素,如果单独加进去就会频繁的回流+重绘,此时我们就可以使用document.createDocumentFragment()将多个img合并到Fragment中再统一加入。
    • 避免频繁读取会引发回流/重绘的属性(见上),如果确实需要多次使用,就用一个变量缓存起来。
    • 将动画效果应用到position属性为absolute或fixed的元素上,对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

说一下浏览器缓存

前端缓存
image.png


强缓存和协商缓存(web性能优化)

  • nodejs实现强缓存和协商缓存图解流程详解参考参考1/参考2前端缓存最佳实践DNS缓存、CDN缓存、浏览器缓存
  • 缓存是Web性能优化的重要方式
  • 缓存分为两种:强缓存和协商缓存,根据响应的header内容来决定
  • 注意:强缓存也会有状态码,是200
  • 区别:如果浏览器命中强缓存,则在设置好的失效时间内不需要给服务器发请求;而协商缓存最终由服务器来决定是否使用缓存,即客户端与服务器之间存在一次通信
  • 总之,强缓存就是强制从缓存中读取,不管是否更新。而协商缓存就是和 服务器协商是否从缓存取,如有更新则返回新数据。
    image.png
  • 强缓存是利用http的返回(响应)头中的Expires或者Cache-Control两个字段来控制的,用来表示资源的缓存时间。如果cache-control与expires同时存在的话,cache-control的优先级高于expires。
    • Expires是http1.0时的规范,它的值为一个绝对时间的GMT格式的时间字符串。缺点:由于失效时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。
    • Cache-Control是http1.1时出现的header信息,主要是利用该字段的max-age值来进行判断,它是一个相对时间,例如Cache-Control:max-age=3600,代表着资源的有效期是3600秒。
      • Cache-Control除了max-age还有以下常用字段
        • no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。
        • no-store:直接禁止浏览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。
        • public:可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。
        • private(默认):只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存。
        • Cache-Control图解参考
  • 协商缓存相关字段有 http响应头中的Last-Modified/请求头中的If-Modified-Since,http响应头中的Etag/请求头中的If-None-Match具体参考协商缓存
    • 通过响应头的2个字段 设置协商缓存:
      • etag:每个文件有一个,改动文件了就变了,就是个文件hash,每个文件唯一,就像用webpack打包的时候,每个资源都会有这个东西,如: app.js打包后变为 app.c20abbde.js,加个唯一hash,也是为了解决缓存问题。
      • last-modified:文件的修改时间,精确到秒
  • 浏览器在第一次请求发生后,再次请求时
    • 浏览器会先获取该资源缓存的header信息,根据其中的expires和cahe-control判断是否命中强缓存,若命中则直接从缓存中获取资源,包括缓存的header信息,本次请求不会与服务器进行通信;
    • 如果没有命中强缓存,浏览器会发送请求到服务器,该请求会携带第一次请求返回的有关缓存的header字段信息(IF-Modified-Since、IF-None-Match,这2个字段值是上次响应头中的Last-Modified和Etag保存而来),由服务器根据请求中的相关header信息来对比结果是否命中协商缓存
    • 若命中协商缓存,则服务器返回新的响应header信息(Last-Modified和Etag)更新缓存中的对应header信息,但是并不返回资源内容,它会告知浏览器可以直接从缓存获取(304);否则返回最新的资源内容(Last-Modified和Etag以及200状态码)
  • 访问缓存优先级
    • 先在内存中查找,如果有,直接加载。
    • 如果内存中不存在,则在硬盘中查找,如果有直接加载。
    • 如果硬盘中也没有,那么就进行网络请求
    • 请求获取的资源缓存到硬盘和内存。
  • DNS缓存、CDN缓存、浏览器缓存
  • 补充:后端设置强缓存与协商缓存的方法(node为例
    • 强缓存(10秒有效期):
      • res.setHeader('Expires',new Date(Date.now()+10*1000).toGMTString())
      • res.setHeader('Cache-Control','max-age=10')
    • 协商缓存:
      • Last-Modified相关设置:
1
2
3
4
5
6
7
8
9
// 服务端设置Last-Modified后,浏览器请求同一个资源会带if-modified-since的请求头
// 所以服务端还要获取这个请求头的时间进行对比,相等返回304提前关闭请求即可,浏览器会自己去缓存里拿
let ctime = 最后一次修改的时间Date对象.toGMTString()
res.setHeader('Last-Modified',ctime)//设置
let ifModifiedSince =req.headers['if-modified-since']//获取
if(ifModifiedSince===ctime){
res.statusCode=304
return res.end()//直接返回
}
    - **etag设置:**
1
2
3
4
5
6
7
8
9
10
// 要稍微复杂点,主要看摘要算法,一般的摘要算法是用md5,由于md5会暴力破解,所以也可以采用sha1或者sha256等加盐算法,其中盐值就是相当于自己的一个密码本,要暴力破解必须知道盐值才行。
// 这些算法在nodejs自带的crypto里都有
let content = await fs.readFile(currentpath,'utf8')
let hash = crypto.createHash('md5').update(content).digest('base64')
res.setHeader('Etag',hash)
let ifNoneMatch =req.headers['if-none-match']
if(ifNoneMatch===hash){
res.statusCode=304
return res.end()
}

前端缓存最佳实践

  • 前端缓存最佳实践:缓存的意义就在于减少请求,更多地使用本地的资源,给用户更好的体验的同时,也减轻服务器压力。所以,最佳实践,就应该是尽可能命中强缓存,同时,能在更新版本的时候让客户端的缓存失效
    • 更新版本的时候,顺便把静态资源的路径改了,这样,就相当于第一次访问这些资源,就不会存在问题了(webpack打包时会把静态资源的路径加上hash值)
    • 较为合理的缓存方案:
      • HTML:使用协商缓存。
      • CSS&JS&图片:使用强缓存,文件命名带上hash值。
  • 在做前端缓存时,我们尽可能设置长时间的强缓存,通过文件名加hash的方式来做版本更新。在代码分包的时候,应该将一些不常变的公共库独立打包出来,使其能够更持久的缓存。
,