HTTP 实战指南 | 青训营笔记

90 阅读10分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 11 天

HTTP 实用指南

初识

从在地址栏输入一个地址到页面渲染的大致流程:

url-parse.png

网络协议模型主要有 OSI 和 TCP/IP 两种,HTTP 协议是两个模型中应用层的传输协议。

Hyper Text Transfer Protocol(HTTP),超文本传输协议,是应用层协议,底层基于 TCP 协议,无状态,简单可扩展。

http-framework.png

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)

https.png

报文解析

请求方法

能在服务器端有操作的是非安全方法,例如: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。

  1. 每一个 URI 代表一种资源
  2. 客户端和服务器之间,传递这种资源的某种表现层
  3. 客户端通过 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: 最后修改时间

缓存流程:

cache-process.png

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 + CookieJWT(JSON web token)

session+cookie.png

jwt.png

适合使用 jwt 的场景:有效期短,只希望被使用一次。比如,用户注册后发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性(通常只允许几小时之内激活),不能被篡改以激活其他可能的账户,一次性的。这种场景就适合使用 jwt。

而由于 jwt 具有一次性的特性。单点登灵和会话管理非常不适合用 jwt,如果在服务端部署额外的逻辑存储 jwt 状态,那还不如 session。基于 session 有很多成熟的框架可以开箱即用,但是用 jwt 还要自己实现逻辑。

单点登录

sso.png

跨域

跨域案例:

Origin A: www.example.com:443

Origin B
www.evil.com:443cross-origin: different domains
example.com:443cross-origin: different subdomains
login.example.com:443cross-origin: different subdomains
www.example.com:443cross-origin: different schemes
www.example.com:80cross-origin: different ports
www.example.com:443same-origin: exact match
www.example.comsame-origin: implicit port number (443) matches

解决方案:

  • CORS

  • 代理服务器

    • 同源策略是浏览器的安全策略,不是 HTTP 的
  • Iframe

proxy-server.png

实战

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'))
});

网络优化

net-optimize.png

稳定性

stability.png

了解更多

WebSocket

  • 浏览器与服务器进行全双工通讯的网络技术
  • 典型场景:实时性要求高,例如聊天室
  • URL 使用 ws:// 或 wss:// 等开头

QUIC

QUIC:Quick UDP Internet Connection

  • 0-RTT 建联(首次建联除外)
  • 类似 TCP 的可靠传输
  • 类似 TLS 的加密传输,支持完美前向安全
  • 用户空间的拥塞控制,最新的 BBR 算法
  • 支持 HTTP2 的基于流的多路复用,但没有 TCP 的 HOL 问题
  • 前向纠错 FEC
  • 类似 MPTCP 的 Connection migration