这是我参与「第五届青训营 」伴学笔记创作活动的第 11 天
HTTP 实用指南
初识
从在地址栏输入一个地址到页面渲染的大致流程:
网络协议模型主要有 OSI 和 TCP/IP 两种,HTTP 协议是两个模型中应用层的传输协议。
Hyper Text Transfer Protocol(HTTP),超文本传输协议,是应用层协议,底层基于 TCP 协议,无状态,简单可扩展。
TCP 协议的特点:
- 面向连接
- 点对点(一对一)
- 可靠交付
- 面向字节流。也就是说仅仅把上层协议传递过来的数据当成字节传输。
为了实现 TCP 上述的特点,TCP 协议需要解决的是面向连接(建立连接和关闭连接的方式)、可靠传输(错误确认和重传)、流量控制(发送方和接收方的传输速率协调)、拥塞控制四个多方面。
协议分析
发展历程
-
HTTP/0.9,有严重的设计缺陷,只用于与老客户端的交互。
- 只有 GET 请求,只能响应 html 文档
-
HTTP/1.0,第一个得到广泛使用的版本。
- 增加了 Header
- 增加了状态码
- 支持多种文档类型,例如 css、js 等
-
HTTP/1.0+,非官方的 HTTP/1.0 的扩展版本。
-
HTTP/1.1,当前使用的版本,它修复了相关设计缺陷,增加相关特性。
- 链接复用
- 缓存
- 内容协商
-
HTTP/2.0,下一代 HTTP 协议版本,目前只用于 https:// 网址。
- 二进制协议
- 压缩 header
- 服务器推送
-
HTTP/3 草案
HTTP/2 概述
HTTP/2 的特点是更快、更稳定和更简单。
帧 (frame): HTTP/2 通信的最小单位每个帧都包含帧头,至少也会标识出当前帧所属的数据流。
消息: 与逻辑请求或响应消息对应的完整的一系列帧。
数据流: 已建立的连接内的双向字节流可以承载一条或多条消息。
HTTP/2 特性:
- 二进制
- 交错发送,接收方重组织
- HTTP/2 连接都是永久的,而且仅需要每个来源一个连接
- 流控制:阻止发送方向接收方发送大量数据的机制
- 服务器推送
HTTPS概述
- HTTPS:Hypertext Transfer Protocol Secure
- 经过TSL/SSL加密
- 对称加密:加密和解密都是使用同一个密钥
- 非对称加密:加密和解密需要使用两个不同的密钥,公钥 (public key)和私钥 (private key)
报文解析
请求方法
能在服务器端有操作的是非安全方法,例如:POST、PUT、DELETE;不在服务器端有操作的就是安全方法,例如:GET、HEAD,当然安全方法并非不能在服务端有操作,这可以由 Web 开发者决定。
HTTP 协议的请求方法详细如下:
- GET:用于请求服务器发送某个资源
- POST:用于向服务器端发送数据
- DELETE:用于向服务器删除某个指定的资源
- PUT:用于向服务器端修改、插入数据
- HEAD:与 GET 方法类似,区别是不返回主体
- TRACK:用于向服务器端告知其支持什么功能
- OPTIONS:用于请求服务器告知其支持什么功能
除了安全和非安全请求,还有幂等(Idempotent)请求:同样请求被执行一次与连续请求多次的效果是一样的,服务器的状态也是一样的,所有 safe 的方法都是 Idempotent 的。属于幂等请求的有:GET、HEAD、OPTIONS、PUT、DELETE。
状态码类型:
- 100 - 199 信息性状态码
- 200 - 299 成功状态码(常见 200 表示请求成功)
- 300 - 399 重定向状态码(常见 302 重定向)
- 400 - 499 客户端错误状态码(常见 404,请求资源不存在)
- 500 - 599 服务端错误状态码
常见状态码:
- 200 0K - 客户端请求成功
- 301 - 资源 (网页等) 被永久转移到其它 URL
- 302 - 临时跳转
- 401 Unauthorized - 请求未经授权
- 404 - 请求资源不存在,可能是输入了错误的 URL
- 500 - 服务器内部发生了不可预期的错误
- 504 Gateway Timeout-网关或者代理的服务器无法在规定的时间内获得想要的响应。
RESTful API
一种 API 设计风格,REST - Representational State Transfer。
- 每一个 URI 代表一种资源
- 客户端和服务器之间,传递这种资源的某种表现层
- 客户端通过 HTTP method,对服务端资源进行操作,实现”表现层状态转化“。
常用请求头
| 请求头 | 描述 |
|---|---|
| Accept | 接收类型,表示浏览器支持的 MIME 类型 (对标服务端返回的 Content-Type) |
| Content-Type | 客户端发送出去实体内容的类型 |
| Cache-Control | 指定请求和响应遵循的缓存机制,如 no-cache |
| If-Modified-Since | 对应服务端的 Last-Modified,用来匹配看文件是否变动,只能精确到 1s 之内 |
| Expires | 缓存控制,在这个时间内不会请求,直接使用缓存,服务端时间 |
| Max-age | 代表资源在本地缓存多少秒,有效时间内不会请求,而是使用缓存 |
| lf-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 头部 (警如为*) |
缓存
-
强缓存
-
Expires,时间戳
-
Cache-Control
-
可缓存性
- no-cache:协商缓存
- no-store:使用任何缓存
-
到期
- max-age:位是秒,存储的最大周期相对于请求的时间
-
重新验证*重新加载
- must-revalidate:一旦资源过期,在成功向原始服务器验证之前,不能使用
-
-
-
协商缓存
- Etag/lf-None-Match: 资源的特定版本的标识符,类似于指纹
- Last-Modified/lf-Modified-Since: 最后修改时间
缓存流程:
Cookie
Cookie 是一种功能强大且高效的持久身份识别技术。它是当前识别用户,实现持久会话的最好方式。
Cookie 分为会话 Cookie(非持久 Cookie)和 持久 Cookie。
用户第一次请求服务器时,服务器返回一个带 Set-Cookie(Set-Cookie1)首部的报文,值为键值对,描述了 Cookie 的名字、值、域、路径等信息,然后客户端接下来每次访问服务器的时候都会带上一个 Cookie 首部的报文,它的值刚好是前面响应报文返回的名字键值对,从而达到验证用户身份的信息。
Cookie 的属性:
- domain : Cookie 的域
- allh : 哪些主机可以使用此 Cookie
- path :哪些路径能使用 Cookie
- secure : 是否在发送 HTTPS 报文的时候使用 Cookie
- expires : 过期时间
- name : Cookie 的名字
- value : Cookie 的值
常见场景
静态资源
静态资源请求的状态码是 200 不一定经过网络请求,也有可能是从缓存读取出来的。
静态资源部署方案:缓存 + CDN + 文件名 hash
- CDN:Content Delivery Network
- 通过用户就近性和服务器负载的判断,CDN 确保内容以一种极为高效的方式为用户的请求提供服务
登录
-
业务场景
- 表单登录
- 扫码登录
-
技术方式
- SSO
登录过程在地址上操作:
- 使用 POST 方法
- 设置目标域名 sso.toutiao.com
- 设置目标 path/quick_login/v2/
在登录过程中携带和返回的信息:
-
携带信息
- Post body,数据格式为 form
- 希望获取的数据格式 json
- 已有的 cookie
-
返回信息
- 数据格式 json
- 设置 cookie 信息
鉴权
鉴权一般有两种方案:Session + Cookie 和 JWT(JSON web token)
适合使用 jwt 的场景:有效期短,只希望被使用一次。比如,用户注册后发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性(通常只允许几小时之内激活),不能被篡改以激活其他可能的账户,一次性的。这种场景就适合使用 jwt。
而由于 jwt 具有一次性的特性。单点登灵和会话管理非常不适合用 jwt,如果在服务端部署额外的逻辑存储 jwt 状态,那还不如 session。基于 session 有很多成熟的框架可以开箱即用,但是用 jwt 还要自己实现逻辑。
单点登录
跨域
跨域案例:
Origin A: www.example.com:443
| Origin B | |
|---|---|
| www.evil.com:443 | cross-origin: different domains |
| example.com:443 | cross-origin: different subdomains |
| login.example.com:443 | cross-origin: different subdomains |
| www.example.com:443 | cross-origin: different schemes |
| www.example.com:80 | cross-origin: different ports |
| www.example.com:443 | same-origin: exact match |
| www.example.com | same-origin: implicit port number (443) matches |
解决方案:
-
CORS
-
代理服务器
- 同源策略是浏览器的安全策略,不是 HTTP 的
-
Iframe
实战
AJAX 之 XHR
function request(option) {
if (String(option) !== '[object Object]') return undefined
option.method = option.method ? option.method.toUpperCase() : 'GET'
option.data = option.data || {}
var formData = []
for (var key in option.data) {
formData.push(''.concat(key, '=', option.data[key]))
}
option.data = formData.join('&')
if (option.method === 'GET') {
option.url += location.search.length === 0 ? ''.concat('?', option.data) : ''.concat('&', option.data)
}
var xhr = new XMLHttpRequest()
xhr.responseType = option.responseType || 'json'
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
if (option.success && typeof option.success === 'function') {
option.success(xhr.response)
}
} else {
if (option.error && typeof option.error === 'function') {
option.error()
}
}
}
}
xhr.open(option.method, option.url, true)
if (option.method === 'POST') {
xhr.setRequestHeader('Content-Type', 'application/x-ww-form-urlencoded')
}
xhr.send(option.method === 'POST' ? option.data : null)
}
AJAX 之 Fetch
- XMLHttpRequest 的升级版
- 使用 Promise
- 模块化设计,Response
- Request,Header 对象
- 通过数据流处理对象,支持分块读取(在大数据处理场景下有奇效)
postData('http://example.com/answer', { answer: 42 })
.then(data => console.log(data)) // JSON from `response.json()` call
.catch(error => console.error(error))
function postData(url, data) {
// Default options are marked with *
return fetch(url, {
body: JSON.stringify(data), // must match 'Content-Type' header
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, same-origin, *omit
headers: {
'user-agent': 'Moilla/4.0 MDN Example',
'content-type': 'applicati,on/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()) // parses response to JSON
}
AJAX 之 node 篇
const https = require('https');
https.get('https://test.com?api_key=DEMO_KEY', (resp) => {
let data = '';
// A chunk of data has been recieved
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);
});
AJAX 之 axios
// 全局配置
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
- 0-RTT 建联(首次建联除外)
- 类似 TCP 的可靠传输
- 类似 TLS 的加密传输,支持完美前向安全
- 用户空间的拥塞控制,最新的 BBR 算法
- 支持 HTTP2 的基于流的多路复用,但没有 TCP 的 HOL 问题
- 前向纠错 FEC
- 类似 MPTCP 的 Connection migration