初识 HTTP:网络通讯模型、HTTP 模型
协议分析:发展历史、HTTP/1.1 报文分析
场景分析:静态资源、登录、视频播放、文件上传、跨域
应用:XHR、Fetch、Node 标准库、Axios、体验优化
其他协议扩展:WebSocket、QUIC
# HTTP 实用指南 - 笔记
# 初识 HTTP
# 网络通信模型
网络通信模型如下图所示,HTTP 协议位于模型顶层
# HTTP 模型
HTTP( Hyper Text Transfer Protocol
) 模型如下图所示,是一个应用层(见上图)的协议,基于 TCP 协议,它有如下特点:
- 支持多种文件格式的传输
- 请求 ↔ 响应 (一个请求对应一个响应)
- 简单可扩展(除了 HTTP 标准定义的 HTTP 头外,可由客户端和服务器自行协定新的 HTTP 头)
- 无状态
# 协议分析
# HTTP 协议发展历史
协议版本 | 简介 | 内容 |
---|---|---|
HTTP/0.9 | 单行协议 | 请求只有 GET,响应只有 HTML 文档 |
HTTP/1.0 | 构建可扩展性 | 增加了 Header,有了状态码,开始支持多种文档类型 |
HTTP/1.1 | 标准化协议 | 链接复用,缓存,内容协商 |
HTTP/2 | 更优异的表现 | 二进制协议,压缩 Header,服务器推送 |
HTTP/3 | 草案 | 未来的协议 |
# 报文分析(HTTP/1.1)
- 请求报文:
- 起始行(格式:请求方法 请求资源路径 HTTP 版本)
- 请求头
- 空行
- 请求正文
- 响应报文:
- 起始行(格式:HTTP 版本 状态码 状态信息)
- 响应头
- 空行
- 响应正文
# HTTP Method
GET
- 请求一个指定资源的表示形式,使用 GET 的请求应该只被用于获取数据POST
- 用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用PUT
- 用请求有效载荷替换目标资源的所有当前表示DELETE
- 删除指定的资源HEAD
- 请求一个与 GET 请求的响应相同的响应,但没有响应体CONNECT
- 建立一个到由目标资源标识的服务器的隧道OPTIONS
- 用于描述目标资源的通信选项TRACE
- 沿着到目标资源的路径执行一个消息环回测试PATCH
- 用于对资源的部分修改
方法分类:
- 安全的:(不会修改服务器数据)
- GET HEAD OPTIONS
- 幂等的:(同样的请求多次执行效果相同)
- GET HEAD OPTIONS PUT DELETE
# 状态码
大类划分:
1xx
- 指示信息,表示信息已接受,继续处理2xx
- 成功,表示请求已被成功接收 / 理解 / 接受3xx
- 重定向,要完成请求必须进行更进一步的操作4xx
- 客户端错误,请求有语法错误或请求无法实现5xx
- 服务器端错误,服务器未能实现合法的请求
常见状态码:
200
OK - 客户端请求成功301
- 资源(网页等)被永久转移到其他 URL302
- 临时跳转401
Unauthorized - 请求未经授权404
Not Found - 请求资源不存在,可能是输入了错误的 URL500
- 服务器内部发生了不可预期的错误504
Gateway Timeout - 网关或者代理的服务器无法在规定的时间内获得想要的响应
# RESTful API
一种 API 设计风格;REST ( Representation State Transfer
) 有以下特点:
- 每一个 URI 代表一种资源
- 客户端和服务器之间,传递这种资源的某种表现层
- 客户端通过 HTTP Method,对服务器资源进行操作,实现 “表现层状态转化”
# 常用请求头
- Accept
- 接收类型,表示浏览器支持的 MIME 类型 (对标服务端返回的 Content-Type)
- Content-Type
- 客户端发送出去实体内容的类型
- Cache-Control
- 指定请求和响应遵循的缓存机制,如 no-cache
- If-Modified-Since
- 对应服务端的 Last-Modified,用来匹配看文件是否变动,只能精确到 1s 之内
- Expires
- 缓存控制,在这个时间内不会请求,直接使用缓存,服务端时间
- Max-age
- 代表资源在本地缓存多少秒,有效时间内不会请求,而是使用缓存
- If-None-Match
- 对应服务端的 ETag,用来匹配文件内容是否改变(非常精确)
- Cookie
- 有 cookie 并且同域访问时会自动带上
- Referer
- 该页面的来源 URL(适用于所有类型的请求,会精确到详细页面地址,CSRF 拦截常用到这个字段)
- Origin
- 最初的请求是从哪里发起的(只会精确到端口),Origin 比 Referer 更尊重隐私
- User-Agent
- 用户客户端(浏览器标识)的一些必要信息,如 UA 头部等
# 常用响应头
- Content-Type
- 服务端返回的实体内容的类型
- Cache-Control
- 指定请求和响应遵循的缓存机制,如 no-cache
- Last-Modified
- 请求资源的最后修改时间
- Expires
- 应该在什么时候认为文档已经过期,从而不再缓存它
- Max-age
- 客户端的本地资源应该缓存多少秒,开启了 Cache-Control 后有效
- ETag
- 资源的特定版本的标识符,ETags 类似于指纹
- Set-Cookie
- 设置和页面关联的 cookie,服务器通过这个头部把 cookie 传给客户端
- Server
- 服务器的一些相关信息
- Access-Control-Allow-Origin
- 服务器端允许的请求 Origin 头部(譬如为 *)
# 缓存控制
# Cookie
Cookie 是呈键值对出现的,具体键值信息如下:
- Name=value
- 各种 cookie 的名称和值
- Expires=Date
- Cookie 的有效期,缺省时 Cookie 仅在浏览器关闭之前有效。
- Path=Path
- 限制指定 Cookie 的发送范围的文件目录,默认为当前
- Domain=domain
- 限制 cookie 生效的域名,默认为创建 cookie 的服务域名
- secure
- 仅在 HTTPS 安全连接时,才可以发送 Cookie
- HttpOnly
- JavaScript 脚本无法获得 Cookie
- SameSite=[None|Strict|Lax]
- None 同站、跨站请求都可发送
- Strict 仅在同站发送
- 允许与顶级导航一起发送,并将与第三方网站发起的 GET 请求一起发
# HTTP/2
更快、更稳定、更简单
- 帧(frame):HTTP/2 通信的最小单位,每个帧都包含帧头,至少也会标识出当前帧所属的数据流
-
消息:与逻辑请求或响应消息对应的完整的一系列帧
-
数据流:已建立的连接内的双向字节流,可以承载―条或多条消息
-
HTTP/2 连接都是永久的,而且仅需要每个来源一个连接
-
流控制:阻止发送方向接收方发送大量数据的机制
-
服务器推送
# HTTPS
HTTPS 建立在 HTTP 之上,在 HTTP 与 TCP/IP 中间插了一层 SSL/TLS(加密层),其特点如下:
- 经过 SSL/TLS 加密
- 对称加密与非对称加密结合
- 随机 key 使用非对称加密传输
- 报文内容使用对称加密传输(随机 key)
# 场景分析
# 静态资源
静态资源解决方案:
缓存 + CDN ( Content Delivery Network
) + 文件名 hash
通过用户就近性和服务器负载的判断,CDN 确保内容以一种极为高效的方式为用户的请求提供服务
# 登录
网站记住登录态主要靠两种方式来鉴权:
- Session + Cookie
- JWT(
JSON web token
)
- SSO(Single Sign On):单点登录
# 视频播放
视频播放的 Response 状态码一般为 206 Partial Content
,意为返回部分资源,资源的长度由 Range 和 Content-range 字段决定,类型由 Content-type 决定
视频直播协议有:
协议 | 描述 | 优点 |
---|---|---|
HLS | HTTP Live Streaming ,Apple 公司基于 HTTP 协议 把一段视频流,分成一个个小的基于 HTTP 的文件来下载 |
跨平台 |
RTMP | Real Time Messaging Protocol ,Adobe 公司基于 TCP |
时延低 |
HTTP-FLV | 基于 HTTP http+flv,将音视频数据封装成 FLV 格式,然后通过 HTTP 协议传输给客户端 |
时延低 |
# 文件上传
小文件直传,大文件分片上传,在服务器整合
# 跨域解决方案
-
CORS
- Access-Control-Allow-Origin
- Access-Control-Expose-Headers
- Access-Control-Max-Age
- Access-Control-Allow-Credentials
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
- Access-Control-Request-Method
- Access-Control-Request-Headers
- Origin
-
代理服务器
- 同源策略是浏览器的安全策略,不是 HTTP 的
-
Iframe
- 不方便,现在几乎没有人用
# 应用
# AJAX:XHR
- XMLHttpRequest
- readyState
- 容易造成回调地狱
readyState | 状态名 | 含义 |
---|---|---|
0 | UNSENT | 代理被创建,但尚未调用 open () 方法。 |
1 | OPENED | open () 方法已经被调用。 |
2 | HEADERS_ RECEIVED | send () 方法已经被调用,并且头部和状态已经可获得。 |
3 | LOADING | 下载中;responseText 属性已经包含部分数据。 |
4 | DONE | 下载操作已完成。 |
function get() { | |
var xhr = new XMLHttpRequest() | |
//open 后面有三个参数: | |
// 规定请求的类型、URL 以及是否异步处理请求。 | |
//method:请求的类型;GET 或 POST | |
//url:文件在服务器上的位置 | |
//async:true(异步)或 false(同步) 默认为 true | |
xhr.open('get', '/getUser?id=xxx') | |
// 发送请求到后端(服务器) | |
xhr.send() | |
// 当请求被发送到服务器时,我们需要执行一些基于响应的任务。 | |
// 每当 readyState 改变时,就会触发 onreadystatechange 事件。 | |
//readyState 属性存有 XMLHttpRequest 的状态信息。 | |
// 在 xhr 的准备状态发生改变的时候,调用该方法 | |
xhr.onreadystatechange = function () { | |
if (xhr.readyState == 4 && xhr.status == 200) { | |
console.log(xhr.responseText) | |
} | |
} | |
} | |
function post() { | |
var xhr = new XMLHttpRequest() | |
//post 请求方式,接口后面不能追加参数 | |
xhr.open('post', '/login') | |
// 如果使用 post 请求方式, 而且是以 key=value 这种形式提交的 | |
// 那么需要设置请求头的类型 | |
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded') | |
xhr.send('username=admin&password=123456') | |
xhr.onreadystatechange = function () { | |
if (xhr.readyState == 4 && xhr.status == 200) { | |
console.log(xhr.responseText) | |
} | |
} | |
} |
# AJAX:Fetch
- XMLHttpRequest 的升级版
- 使用 Promise
- 模块化设计,Response/Request/Header 对象
- 通过数据流处理对象,支持分块读取
function postData(url, data) { | |
// 默认的配置选项用 * 标出 | |
return fetch(url, { | |
body: JSON.stringify(data), // 必须与 'Content-Type' 头匹配 | |
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached | |
credentials: 'same-origin', // include, same-origin, *omit | |
headers: { | |
'user-agent': 'Mozilla/4.0 MDN Example', | |
'content-type': 'application/json' | |
}, | |
method: 'POST', // *GET, POST, PUT, DELETE, etc. | |
mode: 'cors', // no-cors, cors, *same-origin | |
redirect: 'follow', // manual, *follow, error | |
referrer: 'no-referrer', // *client, no-referrer | |
}) | |
.then(response => response.json()) //.json () 方法将响应转换为 JSON | |
} | |
postData('http://example.com/answer', {answer: 42}) | |
.then(data => console.log(data)) //data 为 `response.json ()` 返回的 JSON | |
.catch(error => console.error(error)) |
# Node:标准库 HTTP/HTTPS
- 默认模块,无需安装其他依赖
- 功能有限 / 不是十分友好
const https = require('https'); | |
https.get('https://test.com?api_key=DEMO_KEY', (resp) => { | |
let data = ''; | |
// A chunk of data has been received. | |
resp.on('data', (chunk) => { | |
data += chunk; | |
}); | |
// The whole response has been received. Print out the result. | |
resp.on('end', () => { | |
console.log(JSON.parse(data).explanation); | |
}); | |
}).on("error", (err) => { | |
console.log("Error: " + err.message); | |
}); |
import * as http from 'http'; | |
import * as https from 'https'; | |
import { URL } from 'url'; | |
/** | |
* 判断 url 是否支持范围请求 | |
* @param url | |
*/ | |
function isSupportedRange(url: URL | string): Promise<boolean> { | |
return new Promise((resolve, reject) => { | |
if (typeof url === 'string') url = new URL(url); | |
const options: http.RequestOptions = { | |
method: 'HEAD', | |
headers: { | |
'Range': 'bytes=0-', | |
}, | |
}; | |
let req: http.ClientRequest; // 根据 URL 协议,判断使用 http 还是 https 模块发送请求 | |
function callback(response: http.IncomingMessage) { | |
// console.log(response.statusCode); | |
// console.log(response.headers); | |
// 假如在响应中存在 Accept-Ranges 首部(并且它的值不为 “none”),那么表示该服务器支持范围请求。 | |
if (response.statusCode === 206 || (response.headers["accept-ranges"] && response.headers["accept-ranges"] !== 'none')) resolve(true); | |
resolve(false); | |
} | |
switch (url.protocol) { | |
case 'http:': { | |
req = http.request(url, options, callback); | |
break; | |
} | |
case 'https:': { | |
req = https.request(url, options, callback); | |
break; | |
} | |
default: return void resolve(false); | |
} | |
req.addListener('error', (err: Error) => { | |
reject(err); // 请求失败 | |
}); | |
req.end(); // refresh request stream | |
}); | |
} |
# Axios
- 支持浏览器 / Nodejs 环境
- 丰富的拦截器
// 全局配置 | |
axios.defaults.baseURL = "https://api.example.com"; | |
// 添加请求拦截器 | |
axios.interceptors.request.use(function (config) { | |
// 在发送请求之前做些什么 | |
return config; | |
}, function (error) { | |
// 对请求错误做些什么 | |
return Promise.reject(error); | |
}); | |
// 发送请求 | |
axios({ | |
method: 'get', | |
url: 'http://test.com', | |
responseType: 'stream' | |
}).then(function (response) { | |
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg')) | |
}); |
# 用户体验优化
# 网络优化
# 稳定性
- 重试是保证稳定的有效手段、但要防止加剧恶劣情况
- 缓存合理使用,作为最后一道防线
# 其他协议
# WebSocket
- 浏览器与服务器进行双全工通讯
- 适用于实时性要求高的场景,比如聊天室
- URL 使用 ws:// 或 wss:// 等开头
# QUIC
- QUIC(
Quick UDP Internet Connection
) - HTTP3 的草案之一 - 0-RTT 建联
- 类似 TCP 的可靠传输
- 类似 TLS 的加密传输,支持完美前向安全
- 用户空间的拥塞控制,最新的 BBR 算法
- 支持 HTTP2 的基于流的多路复用,但没有 TCP 的 HOL 问题
- 前向纠错 FEC
- 类似 MPTCP 的 Connection migration
# 参考资料
- 字节青训营课程
- HTTP 权威指南