Network
OSI
- 应用层
- 应用层 HTTP, FTP, DNS
- 表示层 JPEG, Crypto, Decrypto
- 会话层 SSL, TLS
- 传输层 TCP, UDP
- 网络层 IP, ICMP, ARP, RIP
三次握手
- seq (sequence number) 序列号, 随机生成
- ack (acknowledgement number) 确认号, ack = seq + 1
- ACK ACK = 1 确认
- SYN (synchronous) SYN 默认 0, SYN = 1 表示请求同步连接
- FIN (finish) FIN 默认 0, FIN = 1 表示请求终止连接
client ----- handshake1 ----> server
====> SYN1 = 1 ====> # C => S 请求同步
====> seq1 ====>
client <---- handshake2 <--------- server
<==== ACK1 = 1 <==== # 确认 SYN1, C => S 同步
<==== SYN2 = 1 <==== # S => C 请求同步
<==== ack1 = seq1 + 1 <==== # 确认收到 seq1
<==== seq2 <====
client ----- handshake3 ---------> server
====> ACK2 = 1 ====> # 确认 SYN2, S => C 同步
====> ack2 = seq2 + 1 ====> # 确认收到 seq2
四次挥手
client ----- handshake1 ----> server
====> FIN1 = 1 ====> # C => S 请求终止
====> seq1 ====>
FIN_WAIT_1
client <---- handshake2 <--------- server
<==== ACK1 = 1 <==== # 第 1 次确认 FIN1
<==== ack1 = seq1 + 1 <==== # 确认收到 seq1
FIN_WAIT_2 服务器发送剩余数据
client <---- handshake3 <--------- server
<==== ACK1 = 1 <==== # 第 2 次确认 FIN1, C => S 终止
<==== FIN2 = 1 <==== # S => C 请求终止
<==== ack1 = seq1 + 1 <==== # 确认收到 seq1
<==== seq2 <====
TIME_WAIT 客户端发送剩余数据, 持续 2MSL
# MSL, Maximum Segment Lifetime 最长报文段寿命, 大约 1-4 分钟
client ----- handshake4 ---------> server
====> ACK2 = 1 ====> # 确认 FIN2, S => C 终止
====> ack2 = seq2 + 1 ====> # 确认收到 seq2
URL
协议 ://域名 /目录名 /文件名
https://www.example.com/path/to/index.html
输入 URL 后:
- DNS 查询
- TCP 三次握手
- HTTP 请求/响应
- 强缓存/协商缓存 (HTML, CSS, JS)
- TCP 四次挥手
- 将 HTML 文件解析为 DOM 树 (DOM, Document Object Model)
- 将 CSS 文件解析为 CSSOM 树 (CSSOM, CSS Object Model)
- 将 DOM 树和 CSSOM 树合并为渲染树 (Render Tree)
DNS 查询顺序
- 浏览器 DNS 有没有? 有则 return
- 操作系统 DNS 有没有? 有则 return
- 本机 /etc/hosts 文件中有没有? 有则 return
- 向域名服务器发送 DNS 查询请求
迭代查询
本地域名服务器 ====> 根域名服务器 .root-servers.net 本地域名服务器 ====> 顶级域名服务器 gtld-servers.net (Generic Top-Level) 本地域名服务器 ====> 权威域名服务器 ns1.alibabadns.com (或智能 DNS)
dns 迭代查询
dig +trace mihoyo.com
dig @8.8.8.8 stackoverflow.com
OPTIONS 预检请求
OPTIONS 请求称为预检请求 (pre-flight request), 属于 CORS 跨域资源共享机制, 浏览器
- 发送跨域请求
- 发送的实际请求中有自定义请求头
前, 会先发送一次 OPTIONS 预检请求, 询问服务器是否允许跨域, 允许哪些 HTTP 请求方法和请求头字段等, OPTIONS 预检请求的目的是确保实际请求对服务器是安全的, OPTIONS 预检请求包含以下请求头字段
- Origin 发送请求的域名
- Access-Control-Request-Method 实际请求将使用的 HTTP 请求方法
- Access-Control-Request-Headers 实际请求将携带的请求头字段
服务器通过请求头告诉浏览器: 允许发送跨域请求的域名, 允许哪些 HTTP 请求方法和请求头字段等
- Access-Control-Allow-Origin 允许发送跨域请求的域名
- Access-Control-Allow-Methods 允许哪些 HTTP 请求方法
- Access-Control-Allow-Headers 允许哪些 HTTP 请求头字段
强缓存, 协商缓存
强缓存, 协商缓存: 浏览器会缓存服务器提供的资源
- 后续请求强缓存的资源, 不会请求服务器, 浏览器直接返回
- 后续请求协商缓存的资源, 仍会请求服务器, 资源未更新时服务器返回 304 Not Modified, 响应体为空; 资源已更新时服务器返回 200 OK, 响应体中携带更新的资源
服务器可以使用响应头中的 Cache-Control 或 Expires 字段设置强缓存, Expires 的优先级高于 Cache-Control
- Cache-Control: max-age=161043261
- Expires: Mon Jan 01 2025 04:32:51 GMT+0800 (GMT+8:00)
// server.js
resp.setHeader("Expires", new Date(Date.now() + 1000 * 10));
resp.setHeader("Cache-Control", "max-age=10");
resp.end(file.toString());
- disk cache 磁盘缓存
- memory cache 内存缓存
Request Method: GET
Status Code: 200 (from disk cache) # 磁盘缓存
Remote Address: 127.0.0.1:3000
Request Method: GET
Status Code: 200 OK (from memory cache) # 内存缓存
Remore Address [::1]:3000
协商缓存
If-None-Match/ETag 的优先级高于 If-Modified-Since/Last-Modified
- If-Modified-Since: Mon Jan 01 2025 04:32:51 GMT+0800 (GMT+8:00)
- Last-Modified: Mon Jan 01 2025 04:32:51 GMT+0800 (GMT+8:00)
如果请求头中的 If-Modified-Since 字段值和服务器资源的最后修改时间 Last-Modified 值相等, 则服务器判断资源未更新, 返回 304 Not Modified, 响应体为空; 如果服务器资源的最后修改时间 Last-Modified 晚于请求头中的 If-Modified-Since 字段值, 则服务器判断资源已更新, 返回 200 OK, 响应体中携带更新的资源
- If-None-Match: "hash_code_or_version_or..."
- ETag: "hash_code_or_version_or..."
如果请求头中的 If-None-Match 字段值和服务器资源的 ETag 值相等, 则服务器判断资源未更新, 返回 304 Not Modified, 响应体为空; 如果请求头中的 If-Modified-Since 字段值和服务器资源的 ETag 值不相等, 则服务器判断资源已更新, 返回 200 OK, 响应体中携带更新的资源
回流和重绘
回流也称为重排
回流 (Reflow) 是指元素的尺寸, 位置等改变时 (例如 weight, height, font-size) 渲染引擎重新计算整个页面布局, 回流后一定有重绘, 性能影响较大
重绘 (Repaint) 是指元素的样式等改变时 (例如 color, background-color), 渲染引擎重新绘制部分元素, 重绘前不一定有回流, 性能影响较小
会导致回流的操作
- 页面首次渲染
- 浏览器窗口 (视口 vw, vh) 尺寸改变
- 元素尺寸 (weight, height, margin, padding, ...) 或位置 (left, top, ...) 改变
- 元素字体大小改变
- 添加或删除可见的 DOM 元素
- 激活 CSS 伪类 (例如 :hover)
CDN, Content Delivery Network
CDN 内容分发网络, 用于优化资源请求时间
跨域
同源策略
- 同源: 主机 (域名), 端口, 协议都相同
- 跨域
- 主机 (域名) 不同
- 端口不同: 80, 8080
- 协议不同: http, https
跨域的解决
- 前后端协商 jsonp
- 前端解决: 使用代理, 只在开发环境中使用
- 后端解决: 设置请求头
- 使用 nginx 代理
前后端协商 jsonp: script 标签的 src 不受同源策略的限制, 可以发送跨域请求, 但只能发送 GET 请求
pnpm i express @types/express
前端解决: 使用代理, 只在开发环境中使用
pnpm i vite -D # webpack
vim /path/to/ts-playground/vite.config.ts
vite.config.ts
export default defineConfig({
server: {
proxy: {
"/api": {
target: "http://localhost:3000",
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, '')
},
},
},
});
修改 package.json
{
"scripts": {
"dev": "vite"
}
}
后端解决
app.get("/api/json", (_req, resp) => {
// 允许任何主机 (域名), 端口, 协议的请求
// resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
resp.json({ name: "express" });
});
使用 nginx 代理
sudo apt install nginx
sudo vim /etc/nginx/sites-available/default
/etc/nginx/sites-available/default
server {
location /api {
proxy_pass http://127.0.0.1:3000;
}
}
AJAX
AJAX: Asynchronous JavaScript And XML
- AJAX 向服务器发送请求并获取数据, 而无需刷新整个页面, 减少请求次数, 减少响应数据量, 减少网络带宽的消耗, 减轻服务器压力, 实现异步, 增量式更新页面
- AJAX 对 SEO 搜索引擎优化劣势较大, 可能有网络安全问题
核心 API
- 创建 xhr 实例
const xhr = new XMLHttpRequest();
- open 方法: 初始化请求, 指定请求方法, 请求 URL, 是否异步 (默认 true)
- send 方法: 发送请求
- onreadystatechange: readyState 改变时, 调用的回调函数
- readyState 0: 已创建 xhr 实例, 未调用 open 方法
- readyState 1: 已调用 open 方法, 未调用 send 方法
- readyState 2: 已调用 send 方法, 已收到服务器返回的响应头
- readyState 3: 正在接收服务返回的数据
- readyState 4: 已收到服务器返回的全部数据
xhr 发送 GET 请求
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:3000/api/json");
xhr.onload = function () {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.log(xhr.status);
}
};
xhr.send(/* params */);
fetch
fetch 默认只支持 GET 和 POST 请求方法
- text() 将响应体解析为文本字符串
- json() 将响应体解析为 JSON 并返回一个 JS 对象
- blob() 将响应体解析为二进制数据, 并返回一个 Blob 对象
- arrayBuffer() 将响应体解析为二进制数据, 并返回一个 ArrayBuffer 对象
- formData() 将响应体解析为 FormData 对象
fetch("http://localhost:3000/api/json")
.then((resp) => resp.text())
.then((resp) => {
console.log(resp);
});
SSE, WebSocket
SSE, Server-Sent Events, 也称为事件流 Event Stream, 基于 HTTP, 利用 HTTP 的长连接特性, 在客户端和服务器间建立持久连接, 实现服务器向客户端实现服务器向客户端的实时数据推送
WebSocket 基于握手协议 (Handshake Protocol), 使用 HTTP/HTTPS 握手以建立连接, 建立连接后, 在 TCP 连接上进行全双工通信. 在客户端和服务器间建立持久连接, 实现客户端和服务器间的实时, 全双工通信
pnpm i ws
pnpm i @types/ws -D
SSE, Server-Sent Events 和 WebSocket 都减少了不必要的请求, 可以实现服务器向客户端的实时数据推送, 不同点:
- SSE 基于 HTTP, 利用 HTTP 的长连接特性, 在客户端和服务器间建立持久连接; WebSocket 基于 TCP
- SSE 可以传输 text 文本字符串和 blob 二进制数据, 只支持单向数据流, 即只支持服务器向客户端推送数据; WebSocket 支持双向数据流, 没有消息大小限制
- SSE 的 readyState:
- CONNECTING 正在建立连接
- OPEN 已建立连接, 正在接收服务器推送的数据
- CLOSED 已关闭连接 SSE 不能手动关闭或重新连接; WebSocket 可以手动开启, 关闭, 重新连接等
- SSE 基于 HTTP, 没有 SSL/TLS 加密, 不安全; WebSocket 有 SSL/TLS 加密, 安全
navigator.sendBeacon
使用 navigator.sendBeacon 实现高效的数据上报
传统的数据上报, 例如 xhr, XMLHttpRequest 或 fetch, 页面卸载时可能丢失数据; navigator.sendBeacon 不受页面卸载的影响, 异步数据上报, 可以发送跨域请求
- navigator.sendBeacon 只能发送 POST 请求
- 不能自定义请求头
- 只能传输少量数据 (<= 64KB)
- 只能传输 ArrayBuffer, ArrayBufferView, Blob, DOMString, FormData 或 URLSearchParams 类型的数据
navigator.sendBeacon("http://localhost:3000/api/beacon");
TLS, SSL
TLS (Transport Layer Security) 和 SSL (Secure Sockets Layer) 提供加密和认证机制, 确保数据传输的隐私
- 对称加密: 密钥加密, 密钥解密, 例: AES
- 非对称加密: 公钥加密, 私钥解密, 例: RSA, 发送方使用接收方的公钥加密数据, 接收方收到加密数据后使用私钥解密
JWT, JSON Web Token
pnpm i jsonwebtoken cors @types/jsonwebtoken @types/cors
- 使用 online/offline 事件监听器, 监听网络连接状态的改变: 在线/离线
- 使用 navigator.onLine 属性, 获取当前的网络连接状态
// 使用 online/offline 事件监听器
window.addEventListener("online", () => {
console.log("online");
});
window.addEventListener("offline", () => {
console.log("offline");
});
// 使用 navigator.onLine 属性
if (navigator.onLine) {
console.log("Online");
} else {
console.log("Offline");
}
if ("connection" in navigator) {
const networkInfo = navigator.connection;
// 当前网络连接的下载速率, 单位 Mbps
console.log("Network downlink:", networkInfo.downlink);
// 当前网络连接的类型: slow-2g, 2g, 3g, 4g
console.log("Network effective type:", networkInfo.effectiveType);
// 当前网络连接的 rtt, 单位 ms
console.log("Network round-trip time:", networkInfo.rtt);
// 是否处于数据节省模式
console.log("Network data-saving mode:", networkInfo.saveData);
} else {
console.log("navigator.connection is not supported.");
}
XSS, Cross-site scripting 跨站脚本攻击
- 反射型 XSS: 非持久型 XSS
http://127.0.0.1:5500/index.html?a=<script>alert(1)</script>
- 存储型 XSS: 持久型 XSS, 会持久化存储, 最严重
- DOM 型 XSS: 例如 document.write, eval, innerHTML, location, v-html
预防 XSS
- 输入过滤, 输出转义
- CSP, Content Security Policy 设置响应头的内容安全策略