URI(Uniform Resource Identifier) 统一资源标志符 URL(Uniform Resource Locator) 统一资源定位器 URN(Uniform Resource Name) 永久统一资源定位符
URL 和 URN 是 URI 的子集,URI 包含 URL 和 URN。
URL 和 URN 的区别:
- URL 访问某一资源,如果资源已经搬迁了,还访问旧的 URL 就会报 404
- URN 访问某一资源,即使资源已经搬迁了,还能能访问到新资源所在的地址
- 浏览器:浏览器是一个 http 的客户端,但是它的功能不止于此,它还可以将请求回来的资源进行解析渲染
- curl
- http传输的数据是未加密的,也就是明文的,不安全;https 是 http + ssl,ssl 协议对 http 协议传输的数据进行加密,也就是说 https 可以对数据进行加密和身份验证更安全
- http的默认端口是80,https的默认端口是443
- http是无状态的,https可以进行身份认证
- https需要申请证书,需要一定的费用
- 客户端使用 https 的 url 访问服务器,需要与服务器建立 ssl 连接
- 服务器会把
证书信息
(包含公钥)发送给客户端 - 客户端和服务器协商 ssl 连接的安全等级,使用
对称加密
生成会话秘钥 - 客户端使用公钥对会话秘钥进行
非对称加密
,发送给服务器 - 服务器利用私钥解密出会话秘钥
- 服务器利用会话秘钥加密数据与客户端进行信息传递
- 服务器利用 hash 算法对原数据进行处理,生成摘要信息,并用
私钥加密
摘要信息(数字签名) - 服务器把加密后的摘要信息和原数据一起发送给客户端
- 客户端使用
公钥解密
出摘要信息 - 客户端利用 hash 算法对原数据进行处理,生成摘要信息
- 客户端比对两个摘要信息,如果一直,则数据没有被篡改
- 首先浏览器读取出证书中的证书所有者和有效期等信息一一验证
- 浏览器开始查找操作系统中已内置的受信任的证书发布机构,与浏览器发送过来的证书颁发者进行比对,来校验证书
是否为合法机构发布的
- 如果找不到,浏览器会报错,说服务器发来的证书不安全;如果找到,取出操作系统中颁发者证书的公钥,对服务器发来的证书中的数字签名进行解密,得到摘要信息
- 浏览器使用相同的 hash 算法,对服务器发来的
证书内容
进行处理,生成摘要信息 - 比对两个摘要信息,如果一致,则服务器发来的证书是合法的
- https 握手阶段耗时较长,会增加页面渲染时间
- https 缓存不如 http 高效,会增加额外的数据开销
- ssl 证书需要一定的费用
- ssl 证书需要绑定IP,同一个 IP 不能绑定多个域名
- 证书发布机构CA
- 有效期
- 公钥
- 证书所有者
- 签名
用户的登录密码还是要加密的。因为 https 通信中间掺杂着许多代理(客户端代理、服务端代理),通过代理就能监控 https 明文数据,从而能过获取到用户未加密的账号和密码
http1.1
缓存处理
:HTTP1.0 主要使用 Expires、Last-modified,HTTP1.1 主要使用 Cache-Control、Etag带宽优化及网络连接的使用
:HTTP1.1 支持断点续传,即返回状态码 206(Partial Content)错误通知的管理
:在 HTTP1.1 中新增了 24 个错误状态响应码,如409(Conflict)表示请求的资源与资源当前的状态冲突了;410(Gone)表示服务器上的某个资源被永久删除了Host头处理
:在 HTTP1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,在请求消息的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个 IP 地址。HTTP1.1 的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误 400(Bad Request)长连接
:HTTP1.1 中默认开启Connection: keep-alive
,一定程度上弥补了 HTTP1.0 每次都要创建连接的缺点
http2.0
新的二进制格式
:HTTP1.x 的解析是基于文本,基于文本协议的格式解析存在天然的缺陷,文本的变现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认 0 和 1 的组合,基于这种考虑,HTTP2.0 的协议解析决定采用二进制,实现方便且健壮header压缩
:HTTP1.x 的 header 带有大量的信息,而且每次都要重复发送,HTTP2.0 使用 encoder 来减少需要传输的 header大小,通讯双方各自 cache 一份 header fields 表,避免了重复 header 的传输,又减少了需要传输的大小服务器推送
:例如我的网页有一个 style.css 的请求,在客户端收到 style.css 数据的同事,服务端会将 style.js 的文件推送给 客户端,当客户端再次尝试获取 style.js 的时候就可以直接从缓存中获取,不用再发请求了多路复用
:- HTTP/1.0 每次请求响应,建立一个 TCP 连接,用完关闭
- HTTP/1.1 长连接,若干个请求排队串行化单线程处理,后面的请求等待前面的请求的返回才能获得执行的机会,一旦有某个请求超时,后续的请求只能被阻塞,毫无办法,也就是人们常说的线头阻塞
- HTTP/2.0 多路复用,多个请求可同时在一个连接上并行执行,某个请求任务耗时严重,不影响到其他连接的正常执行
- http1.0
每个 TCP 连接只能发送一个请求,当服务器响应后,下一次请求需要再次建立 TCP 连接
- http1.1
默认采用长连接,即一个 TCP 连接发送完一个请求后,默认不会关闭 TCP 连接,下个请求还会使用这个连接(Response Header: Connection: keep-alive)
追问:如何关闭长连接呢? 服务器端设置响应头 Connection: close
管道机制:在同一个 TCP 连接里,允许多个请求同时发送,所有的数据通信是有顺序的(A, B, C),但是服务器处理请求还是一个一个来的,所以会有队头阻塞的问题。
- http2.0
加了全双工模式,服务器也能同时处理多个请求了,解决了队头阻塞的问题。 多路复用:没有次序概念了。 加了服务器推送功能。
- http1.1 同一时间一个 TCP 连接只能处理一个请求,采用一问一答的形式,上一个请求响应后才能处理下一个请求。
追问:听说 Chrome 浏览器支持最大 6 个同域请求并发,这是为什么呢? 因为 Chrome 最大支持 6 个 TCP 连接
- http2.0 同域名上所有请求都在单个连接上完成,单个连接上可以并行交错的进行请求和响应
http2.0 是基于二进制帧的协议,而 http1.1 是基于文本分隔解析协议
http1.1 的报文结构里,服务器需要不断的读入字节,直到遇到换行符,处理的顺序是串行的
http2.0 以帧为最小单位,每个帧都会有标识自己属于哪个流,多个帧组成一个流。多路复用其实就是一个 TCP 里存在多条流
http3.0 采用 QUIC(Quick UDP Internet Connections),快速 UDP 互联网连接。QUIC 是基于 UDP 协议的
http3.0 的两个主要特性:
- 线头阻塞问题解决的更为彻底
基于 TCP 的 http2.0 ,尽管在逻辑上来说,不同的流之间相互独立,不会互相影响,但在实际传输方面,数据还是要一帧一帧的发送和接收,一旦某一个流的数据有丢包,则同样会阻塞在它之后传输的数据流传输。而基于 UDP 的 QUIC 协议则可以更为彻底的解决这样的问题,让不同的流之间真正的实现相互独立传输,互不干扰。
- 切换网络时保持连接
当前移动端的应用环境,用户的网络可能会经常切换,比如冲办公室或家里出门,wifi 断开,网络切换为 3G 或 4G。基于 TCP 的协议,由于切换网络后,IP 会改变,因而之前的连接不可能继续保持。而基于 UDP 的 QUIC 协议,则可以内建与 TCP 中不同的连接标识方法,从而在网络完成切换之后,回复之前与服务器的连接。
http跨域是因为浏览器的同源策略,这里的同源是指: 协议 + 域名 + 端口 都得一致; cookie的同源策略是: domain + path,只要求域名一致,即域名一致,协议和端口不一致也是可以共享的;另外就是 path,path 为 / 表示所有 path 都可以共享
浏览器在发送非简单请求时,会先向服务器发送一个 options 预检请求(增加一次http请求),以获知服务器是否允许
该请求
简单请求必须是 get/post/head,并且这些请求的header头只能包含一些特定的字段,并且 Content-Type 的值只能是 text/plain、multipart/form-data、application/x-www-form-urlencoded...
简单请求:
- 必须是 get / post / head 请求
- 请求头只能包含这些字段:Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width
- Content-Type只能是这些值:text/plain、multipart/form-data、application/x-www-form-urlencoded
- 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
- 请求中没有使用 ReadableStream 对象。
强缓存 Expires(HTTP/1.0) / Cache-control(HTTP/1.1)
浏览器对于强缓存的处理:根据第一次请求资源时返回的响应头来确定的
- Expires: 缓存过期时间,用来指定资源到期的时间(HTTP/1.0)
- Cache-Control: cache-control: max-age=2592000 第一次拿到资源后的2592000秒内(30天),再次发送请求,读取缓存中的信息(HTTP/1.1)
- 两者都存在的话,Cache-Control 优先级高于 Expires
cache-control的取值:
- max-age: 浏览器的缓存过期时间
- s-maxage: 代理服务器的缓存过期时间
强缓存是由服务端设置的,并且基于响应头返回给客户端,客户端浏览器接收到响应后,会自己建立缓存机制,不需前端人员写代码处理
Expires 的值是一个绝对时间的 GMT 格式的时间字符串,由于失效时间是一个绝对时间,所以当服务器与客户端之间时间偏差较大时,会导致缓存混乱
Cache-Control 的取值:
- no-store: 直接禁止浏览器缓存数据(强缓存和协商缓存都不缓存),每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源
- no-cache: 不使用本地缓存,需要使用协商缓存
- immutable: 在缓存有效期内,即使用户刷新浏览器也不会向浏览器发起 http 请求
- public: 可以被所有的用户缓存,包括终端用户和 CDN 等中间代理服务器
- private: 只能被终端用户的浏览器缓存,不允许 CDN 等中间代理服务器缓存
协商缓存 Last-Modified(HTTP/1.0) / Etag(HTTP/1.1)
协商缓存就是强制缓存失效后,浏览器携带缓存标识
向服务器发送请求,由服务器根据缓存标识来决定是否使用缓存的过程
- 客户端携带获取的缓存标识发送 HTTP 请求
If-Modified-Since / If-None-Match
If-Modified-Since = Last-Modified 的值,If-None-Match = Etag 的值 - 浏览器根据资源文件是否更新返回: + 2.1 没更新,返回 304,通知客户端读取缓存的信息 + 2.2 更新了,返回 200 及最新的资源信息,以及 Last-Modified / Etag
Etag 是什么?
Etag 是一个数据签名,唯一的,根据资源内容生成的,类似于 webpack 打包出来的 contenthash
为什么会有 Etag ?
Etag 是 HTTP/1.1 新增的,主要是为了解决几个 Last-Modified 难以解决的问题:
- 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅只是修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新 get
- 某些文件修改非常频繁,比如在秒以下的时间内修改了,if-modified-since 能检查到的粒度是秒级的,这种修改无法判断
- 某些服务器不能精确的得到文件的最后修改时间
强缓存和协商缓存的区别: 协商缓存总会和服务器协商,所以协商缓存总会向浏览器发送请求
强缓存和协商缓存都是针对静态资源文件
cookie 是在服务端通过 Set-Cookie 设置的,它是一个键值对,存储在浏览器端的,浏览器在下次发送请求的时候会自动带上 cookie。Set-Cookie 可以设置多个键值对。
cookie 的属性:
- max-age 和 expires 设置过期时间,如果没有设置,知道页面关闭或浏览器关闭前一直有效
- secure 只在 https 的时候发送
- HttpOnly 浏览器无法通过 document.cookie 访问
因为时间过长,客户端保存的 session 数据就会增大,会增加服务器的压力。
因为浏览器不能直接通过域名找到对应的服务器IP地址,所以需要进行 DNS 解析,查找到对应的 IP 地址进行访问。
DNS解析流程:
- 在浏览器中输入域名,操作系统检查浏览器缓存和本地的 hosts 文件中,是否有这个网址记录,有则从记录里面找到对应的 IP 地址,完成域名解析。
- 查找本地 DNS 解析器缓存中,是否有这个网址记录,有则从记录里面找到对应的 IP 地址,完成域名解析。
- 使用 TCP/IP 参数中设置的 DNS 服务器进行查询,如果要查询的域名包含在本地配置区域资源中,则返回解析结果,完成域名解析。
- 检查本地 DNS 服务器是否缓存该网址记录,有则返回解析结果,完成域名解析。
- 本地 DNS 服务器发送查询报文至跟根 DNS 服务器,根 DNS 服务器收到请求后,用顶级域 DNS 服务器地址进行响应。
- 本地 DNS 服务器发送查询道文至顶级域 DNS 服务器,顶级域 DNS 服务器收到请求后,用权威 DNS 服务器地址进行响应。
- 本地 DNS 服务器发送查询报文至权威 DNS 服务器,权威 DNS 服务器收到请求后,用域名的 IP 地址进行响应,完成域名解析。
- 客户端通过向服务器发送一个 SYN 来创建一个主动打开,作为三次握手的一部分,客户端把这段连接的序列号 Seq 设为随机数X
- 服务器端应当为一个合法的 SYN 返回一个 SYN/ACK,ACK的确认码为 X+1,并且 SYN/ACK 包本身又有另一个 Seq 随机序号 Y
- 最后,客户端再发送一个 ACK,当服务器收到这个 ACK 的时候,就完成了三次握手,并进入连接创建状态。此时包序号被设定为收到的确认号 X+1,而响应则为 Y+1
问题:为什么是三次握手,而不是两次或者四次呢?
简单来说,就是要保证在一段有效时间内,双方收到对方的有效信息。A 发送给 B,B 回复 A,A 如果不再回复 B,B 怎么知道 A 可以收到呢
TCP 作为一种可靠传输控制协议,其核心思想:既要保证数据可靠传输,又要提高传输的效率。
- 客户端 A 发送一个 FIN,用来关闭客户端 A 到服务器 B 的数据传送
- 服务器 B 收到这个 FIN 后,它发回一个 ACK,确认序号为收到的序号加1
- 客户端 A 发送 FIN,服务器 B 是可以继续把它需要响应给客户端 A 的数据发送完,然后服务器 B 关闭与客户端 A的连接,发送一个 FIN 给客户端 A
- 客户端 A 发回 ACK 报文确认,并将确认序号设置为收到的序号加1
问题:为什么连接的时候是三次握手,关闭的时候却是四次挥手呢?
- 服务器端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文
- 但关闭连接时,当服务器端收到FIN报文后,很可能并不会立即关闭连接,所以只能先回复一个ACK报文,告诉客户端:“你发送的FIN报文我收到了”,只有等到服务器端所有的报文都发送完后,我才能发送FIN报文,因此不能一起发送,故需要四次握手
- 302 临时重定向,再次刷新浏览器的时候,浏览器还是会先访问原始的路径,然后在访问重定向后的路径
- 301 永久重定向,再次刷新浏览器的时候,浏览器会直接访问重定向后的路径,并且是从缓存中取出重定向的路径的地址,也就是说即使服务器端已经改变了地址(301后反悔),这个时候如果没有刷新浏览器的缓存,还是会去访问之前缓存的重定向后的地址。所以 301 要谨慎使用
作用:
- 限制请求的资源来源
- 报告资源获取越权
使用方式:
- 在服务器端对响应的 header 做限制
- 在 html 的 meta 标签上做限制
- 建立在 TCP 协议之上,服务器端的实现也比较容易
- 与 http 协议有良好的兼容性,默认端口也是 80 和 443,握手阶段采用的是 http 协议
- 数据格式比较轻量,性能开销小,通信高效
- 可以发送文本,也可以发送二进制数据
- 没有同源限制,客户端和任意服务器都可以通信
- 协议标识符是 ws,如果加密,则是 wss
websocket.readyState:
- CONNECTING: 值为0,表示正在连接
- OPEN: 值为1,表示连接成功
- CLOSING: 值为2,表示正在关闭
- CLOSED: 值为3,表示连接已经关闭
- 接收的时候判断数据的类型
ws.onmessage = function (event) {
if (typeof event.data === 'string') {}
// if (event.data instanceof ArrayBuffer) {}
}
- 指定接收的二进制数据的类型
ws.binaryType = 'blob'
ws.onmessage = function (e) {
console.log(e.data.size)
}
// ws.binaryType = 'arrayBuffer'
// ws.onmessage = function (e) {
// console.log(e.data.byteLength)
// }
websocket 是应用层第七层上的一个应用层协议,它必须依赖 http 协议进行依次握手(这是为了兼容所有的浏览器),握手成功后,数据直接从 TCP 通道传输,与 http 无关了。websocket 分为握手和数据传输阶段,即进行 http 握手 + 双工的 TCP 连接。
- 握手阶段
- 客户端发送消息,请求头里还包含:
Upgrade: websocket Connection: Upgrade
这个就是 websocket 的核心了,告诉服务器,我发送的请求要用 websocket 协议。
除此之外,客户端发送消息,请求头里包含:
Sec-Websocket-Key: xxxxxxxxxxx
Sec-Websocket-key 是一个 Base64 encode 的值,这是浏览器随机生成的
Sec-Websocket-Protocol: chat, superchat
是一个用户自定义的字符串,用来区分同 URL 下,不同的服务所需要的协议
Sec-Websocket-Version: 13
告诉服务器使用的 WebSocket Draft (协议版本)
- 服务端返回消息,响应头里包含:
Upgrade: websocket Connection: Upgrade
告诉客户端,我已经切换协议啦
Sec-Websocket-Accept: xxxxxxxxx
这里需要注意的是 Sec-websocket-Accept 的计算方法:base64(hsa1(sec-websocket-key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))
如果这个 Sec-Websocket-Accept 计算错误,浏览器会提示 Sec-Websocket-Accept dismatch,如果返回成功,Websocket 就会回调 onopen 事件
Sec-Websocket-Protocol
表示最终使用的协议