什么是 HTTP
- Hyper Text Transfer Protocol 超文本传输协议
- 应用层协议,基于 TCP 协议
- 分为两部分:请求 & 响应
- 可拓展,比如可以自定义 Header
- 无状态,每个请求之间都是孤立的
HTTP 历史
HTTP/0.9(单行协议)
- 只有一个请求行,没有请求头和请求体
- 请求方法只有 GET
- 响应只有 HTML 文档
- 文件格式只局限于 ASCII 编码
- 存在的问题:
- 只支持 HTML 文件,其余类型文件无法传输
- 文件格式不再仅仅局限于 ASCII 编码
HTTP/1.0(可拓展性)
- 引入了请求头和响应头
- 增加了状态码
- 支持多种的文档类型
- 提供了 Cache 机制(If-Modified-Since、Last-Modified、Expires)
- 请求头加入了 User-Agent
- 存在的问题:
- 每次通信都需要经过建立 TCP 连接、传输数据、断开 TCP 连接三个阶段,开销很大
- 在同一个 TCP 连接里面,数据请求的通信次序是固定的。服务器只有处理完一个请求的响应后,才会进行下一个请求的处理,如果前面请求的响应特别慢的话,就会造成许多请求排队等待的情况,也就是所谓的队头阻塞
- 每个域名绑定唯一 IP 地址,因此一个服务器只支持一个域名
- 需要在响应头设置 Content-Length,然后浏览器再根据设置的数据大小来接收数据,对于动态生成的数据无能为力
- 不支持断点续传(在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度。)
HTTP/1.1(标准化协议)
- 增加了持久连接,默认开启 Connection: Keep-Alive。只要浏览器或服务器没有明确断开连接,那么连接会一直保持
- 虚拟主机的发展可以让一个 IP 对应多个域名。
- 请求头增加了 Host 字段,用来表示当前域名地址
- 域名分片机制:引入 CDN 之后,每个域名可以维护 6 个连接
- 引入了 cookie 机制和安全机制
- 新的缓存方案(If-None-Match、ETag)
- 存在的问题:
- TCP 的慢启动
- 同时开启多条 TCP 连接时,连接之间会竞争带宽
- 队头阻塞问题依然无法解决
- 由于 HTTP 1.1 协议不带有状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。
HTTP/2 (表现更优异)
- HTTP/2 是一个二进制协议。在 HTTP/1.1 中,报文的头信息必须是文本(ASCII 编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为帧
- 实现了多路复用。HTTP/2 仍然复用 TCP 连接,但是在一个连接里,客户端和服务器都可以同时发送多个请求或回应,而且不用按照顺序一一发送,这样就避免了队头阻塞问题
- 使用了数据流的概念。因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的请求。因此,必须要对数据包做标记,指出它属于哪个请求。HTTP/2 将每个请求或回应的所有数据包,称为一个数据流。每个数据流都有一个独一无二的编号。数据包发送的时候,都必须标记数据流 ID ,用来区分它属于哪个数据流。
- 实现了头信息压缩。由于 HTTP 1.1 协议不带有状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。HTTP/2 对这一点做了优化,引入了头信息压缩机制。一方面,头信息使用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就能提高速度了。
- 允许服务器推送。HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送。使用服务器推送,提前给客户端推送必要的资源,这样就可以相对减少一些延迟时间。
- 存在的问题:因为 HTTP/2 使用了多路复用,一般来说同一域名下只需要使用一个 TCP 连接。由于多个数据流使用同一个 TCP 连接,遵守同一个流量状态控制和拥塞控制。只要一个数据流遭遇到拥塞,剩下的数据流就没法发出去,这样就导致了后面的所有数据都会被阻塞。这也导致了队头阻塞。HTTP/2 出现的这个问题是由于其使用 TCP 协议的问题,与它本身的实现其实并没有多大关系。
HTTP/3(QUIC 协议)
- Quick UDP Internet Connection
- 基于 UDP 实现了类似 TCP 的流量控制、可靠传输机制
- 集成了 TLS 安全加密
- 实现了 HTTP/2 多路复用技术,QUIC 实现了在同一个物理连接中可以有多个独立的逻辑数据流,实现了数据流单独传输,解决了 TCP 队头阻塞的问题
- 实现了快速握手功能(因为是基于 UDP 的)
- 存在的问题:
- 服务器和浏览器还没有对其提供较完整的支持
- 可能存在安全性问题
常用请求方法
请求方法 | 说明 |
---|---|
GET | 请求指定的资源,并返回实体 |
POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和已有资源的修改 |
PUT | 从客户端向服务器传送的数据取代指定的内容 |
DELETE | 请求服务器删除指定的数据 |
OPTIONS | 在采取具体资源的请求之前,决定对该资源采取何种必要措施,或者了解服务器的性能 |
CONNECT | HTTP/1.1 协议中预留于能够将连接改为管道方式 |
HEAD | 类似于 GET 请求,只不过返回的响应中没有请求体 |
报文
请求方法
- 安全的方法:不好修改服务器数据,如 GET、HEAD、OPTIONS
- 幂等的方法:同样的请求执行一次,与连续执行多次效果相同。包括上面所有的安全方法,还包括 PUT、DELETE 这两个不安全但是幂等的方法
状态码
状态码概览:
- 1XX:请求已接受,正在继续处理
- 2XX:请求成功,处理完毕
- 3XX:重定向
- 4XX:客户端错误
- 5XX:服务端错误
常见状态码总结:
状态码 & 英文描述 | 详细说明 |
---|---|
100 Continue | 服务器收到了请求的一部分,并且希望客户端继续发送其余部分 |
101 Switching Protocols | 切换协议,服务端根据客户端请求的头信息切换协议 |
200 OK | 请求成功,且被服务端成功处理 |
201 Created | 成功请求,且创建了新的资源 |
202 Accepted | 服务器已接受请求,但未处理完成 |
204 No Content | 请求成功处理,但是没有资源可以返回 |
206 Partial Content | 服务器成功处理了部分 GET 请求 |
301 Moved Permanently | 永久重定向。请求的资源被分配了新的 URL,之后应使用更改的URL |
302 Found | 临时重定向。表示请求的资源被分配了新的 URL,希望本次访问使用新的 URL |
304 Not Modified | 缓存相关的状态码。自从上次请求后,请求的资源未被修改过。 服务器返回此响应时,不会返回任何资源。客户端的请求中带有 If-Modified-Since 或者 If-None-Match |
307 Temporary Redirect | 类似 302,但是 307 会遵照浏览器标准,请求方法不会从 POST 变成 GET |
400 Bad Request | 请求报文中存在语法错误 |
401 Unauthorized | 用户未授权 |
403 Forbidden | 服务器拒绝该次访问 |
404 Not Found | 服务器上无法找到请求的资源 |
408 Request Time-out | 服务器等待客户端发送的请求时间过长,超时 |
500 Internal Server Error | 服务器内部错误,无法完成请求 |
502 Bad Gateway | 服务器作为网关或者代理时,为了完成请求访问下一个服务器,但该服务器返回了非法的应答 |
503 Service Unavailable | 服务器超负载或正在进行停机维护,无法处理请求 |
RESTful API
一种 API 设计风格,有以下特点:
- 每个 URI 代表一种资源
- URI 只用于表示资源的名称,而不包括资源的操作
- 接口应该使用标准的 HTTP 方法如 GET,PUT 和 POST,并遵循这些方法的语义
CRUD 应该遵循以下语义:
示例:
常用请求头
请求头 | 说明 |
---|---|
Accept | 浏览器可接受的 MIME 类型 |
Content-Type | 资源属于什么 MIME 类型 |
Cache-Control | 用于指定缓存机制。常见的值有 no-cache(不直接使用缓存,要向服务器发起请求确认资源是否更改,也就是我们常说的协商缓存),no-store(不使用任何缓存),max-age=xxx(缓存内容在 xxx 秒后失效) |
If-Modified-Since | 对应于服务端的 Last-Modified,若所请求的内容在指定的日期之后没有修改过,则返回 304 Not Modified,精度达到秒 |
If-None-Match | 对应于服务端的 ETag,若所请求的内容在指定的日期之后没有修改过,则返回 304 Not Modified,精度非常准确 |
Cookie | 有 cookie 而且同域的时候会自动带上 |
User-Agent | 浏览器类型 |
Connection | 若为 Keep-Alive,或者协议是 HTTP/1.1,则开启持久连接 |
Location | 一般用来表示重定向的地址 |
Expires | 缓存过期时间,在此时间内不需要发起请求,可以直接使用缓存 |
Authorization | 授权信息 |
Referer | 说明该页面的来源 URL。用处:防止盗链;避免 CSRF 攻击 |
Origin | 类似于 Referer,把 URI 剥离成 {协议,域名,端口} 的三元组,用于指明当前请求来自于哪个站点。Origin 的出现就是为了实现跨域。 |
常用响应头
响应头 | 说明 |
---|---|
Content-Type | 资源属于什么 MIME 类型 |
Cache-Control | 用于指定缓存机制。常见的值有 no-cache(不直接使用缓存,要向服务器发起请求确认资源是否更改,也就是我们常说的协商缓存),no-store(不使用任何缓存),max-age=xxx(缓存内容在 xxx 秒后失效) |
Last-Modified | 最后修改时间。客户端可以通过 If-Modified-Since 请求头提供一个日期,只有改动时间迟于指定时间,才会返回新的资源,否则返回一个 304 Not Modified |
ETag | 资源特定的标识符。客户端可以通过 If-None-Match 请求头提供一个标识符,如果客户端标识符与服务端不同才会返回新的资源,否则返回一个 304 Not Modified |
Set-Cookie | 设置页面相关的 Cookie |
Access-Control-Allow-Origin | 服务器允许请求的 Origin,如果设置为 * 则表示允许所有的 Origin |
Expires | 缓存过期时间。在此时间内不需要发起请求,可以直接使用缓存 |
Max-age | 本地缓存应该缓存多久,开启了 Cache-Control 后才生效 |
缓存
强缓存
强缓存是利用 http 头中的 Expires 和 Cache-Control 两个字段来控制的。强缓存中,当请求再次发出时,浏览器会根据 Expires 和 Cache-control 判断目标资源是否命中强缓存,若命中则直接从缓存中获取资源,不会再与服务端发生通信。
Expires VS max-age
- Expires 是一个时间戳,接下来如果我们试图再次向服务器请求资源,浏览器就会先对比本地时间和 Expires 的时间戳,如果本地时间小于 Expires 设定的过期时间,那么就直接去缓存中取这个资源。由于时间戳是服务器来定义的,而本地时间的取值却来自客户端,因此 Expires 的工作机制对客户端时间与服务器时间之间的一致性提出了极高的要求,若服务器与客户端存在时差,将带来意料之外的结果。
- max-age 是一个相对时间,这就意味着它有能力规避掉 Expires 可能会带来的时差问题。客户端会记录请求到资源的时间点,以此作为相对时间的起点,从而确保参与计算的起始时间和当前时间都来源于客户端,因此能够实现更加精准的判断。
- Cache-Control 的 max-age 配置项相对于 Expires 的优先级更高。当 Cache-Control 与 Expires 同时出现时,我们以 Cache-Control 为准。
协商缓存
浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求,还是从本地获取缓存的资源。如果服务端提示缓存资源未改动,资源会被重定向到浏览器缓存(解释了为什么它是 3XX 开头),这种情况下网络请求对应的状态码是 304。
Last-Modified / If-Modified-Since
含义:指最后一次修改资源的时间。开启了协商缓存之后,我们的每次请求都会带上 If-Modified-Since 的时间戳字段,它的值正是上一次 response 返回给它的 Last-Modified 值。服务器接收到这个时间戳后,会比对该时间戳和资源在服务器上的最后修改时间是否一致,从而判断资源是否发生了变化。如果发生了变化,就会返回一个完整的响应内容,并在 Response Headers 中添加新的 Last-Modified 值,否则返回 304 Not Modified。
弊端:
- 有时候可能我们编辑了文件,但文件的内容没有改变。服务端并不清楚我们是否真正改变了文件,它仍然通过最后编辑时间进行判断。因此这个资源在再次被请求时,会被当做新资源,进而引发一次完整的响应——不该重新请求的时候,也会重新请求。
- 当我们修改文件的速度过快时(比如花了 100ms 完成了改动),由于 If-Modified-Since 只能检查到以秒为最小计量单位的时间差,所以它是感知不到这个改动的——该重新请求的时候,反而没有重新请求了。
Etag / If-None-Match
Etag 是由服务器为每个资源生成的唯一的标识字符串,这个标识字符串是基于文件内容编码的,只要文件内容不同,它们对应的 Etag 就是不同的;相反,只要文件内容相同,ETag 就是相同的。因此 Etag 能够精准地感知文件的变化。它的作用原理和 If-Modified-Since 类似,都是客户端带上然后去跟服务端进行比较,不同就返回资源以及新的 ETag,相同就返回 304 Not Modified。
弊端:Etag 的生成过程需要服务器额外付出开销,会影响服务端的性能,这是它的弊端。Etag 并不能替代 Last-Modified,它只能作为 Last-Modified 的补充存在。 Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。
示例图:
简单来说就是:
- 有强缓存且新鲜,就用,否则,就看有没有协商缓存
- 协商缓存 ETag 优先级高于 If-Modified-Since
- 什么缓存都没有,或者缓存不新鲜,就去重新请求
Cookie
属性 | 说明 |
---|---|
<cookie-name>=<cookie-value> | cookie 的键值对。 |
Path | 指定一个 URL 路径,这个路径必须出现在要请求的资源的路径中才可以发送 cookie。 |
Domain | 指定 cookie 可以送达的主机名。与之前的规范不同的是,域名之前的点号会被忽略。假如指定了域名,那么相当于各个子域名也包含在内了。 |
Sec | 规定必须通过安全的 HTTPS 连接来传输 cookie。 |
HttpOnly | JS 脚本将无法读取到 cookie 信息,这样能有效的防止 XSS 攻击。 |
Expires | 规定 cookie 的最长有效时间。形式为符合 HTTP-Date 规范的时间戳。 |
Max-Age | 在 cookie 失效之前需要经过的秒数。假如 Expires 和 Max-Age 均存在,那么 Max-Age 优先级更高。 |
SameSite=[Strict, Lax] | 允许服务器设定 cookie 不随着跨域请求一起发送,这样可以在一定程度上防范 CSRF 攻击。 |
HTTPS
HTTP 存在的问题
- HTTP 报文使用明文方式发送,可能被第三方窃听。
- HTTP 报文可能被第三方拦截后修改通信内容,接收方没有办法发现报文内容的修改。
- HTTP 还存在认证的问题,第三方可以冒充他人参与通信。
HTTPS 的解决方案
HTTPS 指的是超文本传输安全协议,HTTPS 是基于 HTTP 协议的,不过它会使用 TLS/SSL 来对数据加密。优点有:
- 所有的信息都是加密的,第三方没有办法窃听。
- 它提供了一种校验机制,信息一旦被篡改,通信的双方会立刻发现。
- 配备了身份证书,防止身份被冒充的情况出现。
TLS 握手安全的原理
- 对称加密:双方使用同一个秘钥对数据进行加密和解密。但是对称加密的存在一个问题,就是如何保证秘钥传输的安全性,因为秘钥还是会通过网络传输的,一旦秘钥被其他人获取到,那么整个加密过程就毫无作用了。因此还需要非对称加密。
- 非对称加密:我们拥有两个秘钥,一个公钥,一个私钥。公钥是公开的,私钥是保密的。用私钥加密的数据,只有对应的公钥才能解密;用公钥加密的数据,只有对应的私钥才能解密。我们可以将公钥公布出去,任何想和我们通信的客户,都可以使用我们提供的公钥对数据进行加密,然后我们就可以对应的私钥进行解密,这样就能保证数据的安全了。但是非对称加密有一个缺点就是加密速度很慢,因此如果每次通信都使用非对称加密的方式的话,反而会造成等待时间过长的问题。
- 非对称与对称的权衡:因为对称加密的方式的缺点是无法保证秘钥的安全传输,非对称加密的缺点是加密速度很慢,因此我们可以用非对称加密的方式来对对称加密的密钥进行传输,然后以后的通信使用对称加密的方式来加密,这样就解决了两个方法各自存在的问题。注意,只有传输这个加密的密钥的时候我们才需要使用非对称的加密。
- 数字证书:但是这样依然无法确保安全性。因为我们没有办法确定我们得到的公钥就一定是安全的公钥。可能存在一个中间人,拦截了对方发给我们的公钥,然后将他自己的公钥发送给我们,当我们使用他的公钥加密后发送信息,就可以被他用自己的私钥解密。然后他伪装成我们以同样的方法向对方发送信息,这样我们的信息就被窃取了,然而我们自己还不知道。为了解决这样的问题,我们可以使用数字证书的方式,首先我们使用一种 Hash 算法来对我们的公钥和其他信息进行加密生成一个信息摘要,然后让有公信力的认证中心(简称 CA )用它的私钥对信息摘要加密,形成签名。最后将原始的信息和签名合在一起,称为数字证书。当接收方收到数字证书的时候,先根据原始信息使用同样的 Hash 算法生成一个摘要,然后使用公证处的公钥来对数字证书中的摘要进行解密,最后将解密的摘要和我们生成的摘要进行对比,就能发现我们得到的信息是否被更改了。这个方法最重要的是认证中心的可靠性,一般浏览器里会内置一些顶层的认证中心的证书,相当于我们自动信任了他们,只有这样我们才能保证数据的安全。
示例图:
静态资源
静态资源方案:缓存 + CDN + 文件名 hash(确保用户能拿到最新的文件)
CDN
Content Delivery Network,内容分发网络,是由分布在不同区域的边缘节点服务器组成的分布式网络。开启 CDN 之后,用户的请求并不是直接发送给源网站,而是发送给 CDN 服务器,由 CND 服务器将请求定位到最近的含有该资源的服务器上去请求。这样有利于提高网站的访问速度,同时也分担了源服务器的访问压力。
是否开启 CDN 的区别
不开启 CDN:
用户输入域名 -> DNS 解析获取 IP -> 向该 IP 对应服务器发送访问请求 -> 返回资源
开启 CDN:
用户输入域名 -> 智能 DNS 解析 -> 获取缓存服务器 IP -> 若缓存有目标资源,返回资源;若没有就向源服务器发起请求,把获取的资源保存到缓存服务器,再把资源返回给用户
SSO 单点登录
在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理。
用户在授权访问后会获得一个凭证,之后访问相关的应用时也会带上这个凭证,所以用户就可以在不需要再次登录的情况下直接登录访问。
跨域解决方案
常用的跨域解决方案主要有五种:
- JSONP
- CORS
- WebSocket
- Node 正向代理
- Nginx 反向代理
Ajax
Ajax 五部曲
1 | // 第一步:创建 xhr 对象 |