计算机网络
状态码
301 moved permanently:永久性重定向
if (req.url === '/login') {
console.log(111)
res.writeHead(301, {'Location': 'http://localhost:3000/redirect'})
res.end()
}
else if (req.url === '/redirect') {
console.log(222)
res.end('hello world')
}
当我们多次访问/login
,会输出
111
222
222
222
...
第一次
Request URL: http://localhost:3000/login
Request Method: GET
Status Code: 301 Moved Permanently
第二次
Request URL: http://localhost:3000/login
Request Method: GET
Status Code: 301 Moved Permanently (from disk cache)
302 found:临时性重定向
if (req.url === '/login') {
console.log(111)
res.writeHead(302, {'Location': 'http://localhost:3000/redirect'})
res.end()
}
else if (req.url === '/redirect') {
console.log(222)
res.end('hello world')
}
当我们多次访问/login
,会输出
111
222
111
222
...
多次访问的HTTP报文如下。
Request URL: http://localhost:3000/ddd
Request Method: GET
Status Code: 302 Found
304 Not Modified:用于浏览器缓存。
请求方法
方法 | 描述 |
---|---|
Get | 通常用于请求资源 |
Post | 通常用于向服务端发送资源 |
Delete | 通常用于删除资源 |
Put | 通常用于资源的更新,若资源不存在则新建一个 |
Option | 通常用于CORS的请求预检 |
Head | 只请求资源的头部,该请求方法的一个使用场景是在下载一个大文件前先获取其大小再决定是否要下载, 以此可以节约带宽资源 |
Get/Post的区别
- Get请求的参数放在URL里,Post请求的参数放在实体里。
- Get请求比起Post请求更加不安全,因为参数放在URL中,不能用来传递敏感信息。
- Get请求的参数放在URL中,所以有长度限制;而Post请求没有限制。
RESTful API
应该尽量将API部署在专用域名之下。
https://api.example.com
如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。
https://example.org/api/
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词。
对于资源的具体操作类型,由HTTP动词表示。
如Get, Post, Put, Delete等
HTTP头部
一些常用的请求/响应/通用头部
请求头部
cookie: ''
host: ''
If-None-Match: ''
If-Modified-Since: ''
host 与 虚拟主机
host字段是HTTP1.1新增的头部,主要用来实现虚拟主机
一台物理主机上当然可以在不同端口上部署多个服务端。一台主机也可以给多个不同的域名以供访问。
那么可以通过nginx来实现虚拟主机,配置类似如下。
server
{
listen 80
server_name www.aaa.com;
# 可以在这进行代理
location / {
proxy_pass localhost:3000
}
}
server
{
listen 80
server_name www.bbb.com;
location / {
proxy_pass localhost:8080
}
}
无论是www.aaa.com
还是www.bbb.com
,都能访问我们的服务器。
根据域名/host的不同,代理向不同的服务端。
X-Forwarded-For
当我们的请求通过代理服务器发送给Node服务器,Node服务器使用ctx.ip
拿到的是代理服务器而不是客户端的IP地址。
所以当请求经过代理服务器后,代理服务器会给请求加上一个请求头部X-Forwarded-For
来记录客户端的IP地址。如果请求从客户端->代理服务器A->代理服务器B->Node服务器,Node服务器拿到的请求的X-Forwarded-For
的值为客户端的ip, 代理服务器A的IP
。
有的时候我们可能想使用X-Forwarded-For
拿到源请求的IP地址,但因为请求头可以被客户端伪造,反而会有一定风险。当我们通过X-Forwarded-For
获取用户真实IP时,最好不要取第一个IP。比如我们的最外层代理服务器可以直接覆盖X-Forwarded-For
的值,又或者取IP值的时候从右往左计算。
响应头部
Set-Cookie: ''
Location: '/'
ETag: ''
Last-Modified: ''
Cache-Control: 'max-age='
expires: ''
access-control-allow-origin: '*'
access-control-allow-credentials: true
通用头部
accept: ''
accept-language: ''
content-Type: ''
content-length: ''
HTTP协议对比
HTTP1.1比起1.0
- HTTP1.0默认不开启长连接,HTTP1.1默认开启(Connection:Keep-Alive),并且支持管线化(Pipeline)。
- HTTP1.0不支持Host头部,HTTP1.1支持,可以实现虚拟主机。
- HTTP1.1比1.0新加了E-tag,If-Node-Match,Cache-control等用于缓存控制的头部。
- HTTP1.1新增24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突。
- HTTP1.1对带宽进行优化。
HTTP2.0比起1.1
- HTTP2.0采用的二进制格式传输,取代了HTTP1.x的文本格式的传输。
- 多路复用。在HTTP2.0中有两个概念,分别是帧(frame)和流(stream),帧表示最小的单位,每个帧都会标识出该帧属于哪个流。多路复用指的是一个TCP连接中可以存在多个流,也就是说,同一时间可以发送多个请求。
- 头部压缩。对报文的头部进行压缩,在客户端和服务端都维护着一份字典记录着头部对应的索引。
- 服务端推送(Server Push)。服务端可以预测客户端需要的资源,并主动推送给客户端。
HTTPS
HTTP = HTTP + TLS/SSL
发送HTTPS请求时
- 生成HTTP报文,交给TLS处理,进行TLS握手;交换互相的随机数,支持的加密算法,压缩算法,协议版本号。
- 服务端发送证书给客户端,证书包括服务端的公钥和CA的私钥对服务端公钥的签名。客户端用CA的公钥对签名进行验证。
- 验证成功后,客户端生成预备主密码,用服务端公钥加密后发送给服务端。服务端接收到预备主密码后,结合两个随机数生成主密码。
- 主密码用来生成会话使用的密钥,消息认证码使用的密钥,CBC模式要用到的初始向量。
- 报文分割后,压缩,加上MAC后进行加密传输。
中间人攻击
客户端 <=> 中间人 <=> 服务端
- 服务端向客户端发送公钥,被中间人获取,中间人把自己的公钥给客户端。
- 客户端用中间人的公钥加密数据发送对称密钥,中间人用自己的私钥解密,再用服务端的公钥加密发送,服务端用自己的私钥解密。
- 接下来客户端和服务端用对称密钥通信,然而这个密钥中间人也知道,因此能知道密文对应的明文。
中间人攻击是因为服务端发送过来的公钥无法验证是不是真实的公钥,还是伪造的公钥。因此用CA签名的证书(公钥+签名)即可。
TCP三次握手
三次握手
Client给Server发送报文,Server知道自己能接收到Client发送的报文
该报文的SYN = 1, seq = x
Server给Client发送报文,Client知道自己能接收Server发送的报文,知道自己发送的报文能被Server接收
该报文的SYN = 1, ACK = 1,确认号 = x + 1, seq = y
Client给Server发送报文,Server知道自己发送的报文能被Client接收。
该报文的ACK = 1,确认号 = y + 1
经过三次握手,客户端(Client)和服务端(Server)都知道自己发送的报文能被对方接收,也知道自己能接收到对方的报文。
注:SYN / ACK / FIN 为TCP报文头部的一个标识。seq为报文的序列号(Sequence number),ack为报文的确认序号(并不是之前那个标识,而是Acknowledgement Number)。
SYN = 1,seq = x 对应的是 ACK = 1,ack = x + 1
为什么不两次握手
主要是为了防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
假设有这样一种场景, 客户端发送的第一个请求连接并且没有丢失,但是被滞留的时间太长。由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送报文。 而现在第一个请求到达服务端,这个请求已经报废了,但是又会建立连接。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
为什么不四次握手
既然三次就可以了,多一次就是浪费资源了。
四次挥手
TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK。
- 第一次挥手
- 若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求。
- 第二次挥手
- B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,表示 A 到 B 的连接已经释放,不接收 A 发的数据了。但是因为 TCP 连接时双向的,所以 B 仍旧可以发送数据给 A。
- 第三次挥手
- B 如果此时还有没发完的数据会继续发送,完毕后会向 A 发送连接释放请求,然后 B 便进入LAST-ACK状态。
- PS:通过延迟确认的技术(通常有时间限制,否则对方会误认为需要重传),可以将第二次和第三次握手合并,延迟 ACK 包的发送。
- 第四次挥手
- A 收到释放请求后,向 B 发送确认应答,此时 A 进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态。当 B 收到确认应答后,也便进入 CLOSED 状态。
个人理解:由于在客户端要关闭TCP连接的时候,服务端可能还在发送数据;所以服务端先进行第二次挥手,这个报文的作用是服务端不再接收数据;当服务端的数据全部发送过去后,再一次挥手,这样服务端就不再发送数据了;至此,TCP连接就关闭了。
TIME-WAIT
在握手和挥手的不同阶段,客户端和服务端都处于不同的状态。
在第四次挥手后,客户端会进入 TIME-WAIT状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态
如果没有TIME-WAIT状态,若报文因为网络问题没有送达,则服务端不会正常关闭。
UDP
TCP是面向连接的传输层协议,而UDP是面向无连接的传输层协议。
TCP通过三次握手/四次挥手来保障传输,不过因此速度比UDP慢。
UDP通常用于DNS,或者是一些直播流的传输。
DNS
DNS查询过程
- 浏览器是否有缓存
- 操作系统是否有缓存
- 本地Hosts文件是否有缓存
- 本地DNS服务器是否有缓存
- 向根域名服务器查询,若知道对应IP则返回IP,不知道则告诉本地DNS服务器要去哪个顶级域名服务器查询
- 迭代,直到找到对应的ip
递归
本地 <=> 本地DNS服务器 <=> 权威DNS服务器
迭代
本地DNS服务器 <=> 根域名服务器,若查不到则进行下一步
<=> 顶级域名服务器,若查不到则进行下一步
<=> 二级域名服务器...
解析记录
- A记录,解析域名到IP
- CNAME记录,解析域名到域名
- 其他各种记录
CDN
CDN(Content Delivery Network,内容分发网络)是构建在现有互联网基础之上的一层智能虚拟网络,通过在网络各处部署节点服务器,实现将源站内容分发至所有CDN节点,使用户可以就近获得所需的内容。CDN服务缩短了用户查看内容的访问延迟,提高了用户访问网站的响应速度与网站的可用性,解决了网络带宽小、用户访问量大、网点分布不均等问题。
当用户访问使用CDN服务的网站时,本地DNS服务器通过CNAME方式将最终域名请求重定向到CDN服务。CDN通过一组预先定义好的策略(如内容类型、地理区域、网络负载状况等),将当时能够最快响应用户的CDN节点IP地址提供给用户,使用户可以以最快的速度获得网站内容