Published on

Http笔记(二)

Authors
  • avatar
    Name
    Et cetera
    Twitter

HTTP 报文结构

  1. HTTP 报文结构就像是“大头儿子”,由“起始行 + 头部 + 空行 + 实体”组成,简单地说就是“header+body”
  2. HTTP 报文可以没有 body,但必须要有 header,而且 header 后也必须要有空行,形象地说就是“大头”必须要带着“脖子”
  3. 请求头由“请求行 + 头部字段”构成,响应头由“状态行 + 头部字段”构成
  4. 请求行有三部分:请求方法,请求目标和版本号
  5. 状态行也有三部分:版本号,状态码和原因字符串
  6. 头部字段是 key-value 的形式,用“:”分隔,不区分大小写,顺序任意,除了规定的标准头,也可以任意添加自定义字段,实现功能扩展
  7. HTTP/1.1 里唯一要求必须提供的头字段是 Host,它必须出现在请求头里,标记虚拟主机名

HTTP/1.1 请求方法

  1. GET:获取资源,可以理解为读取或者下载数据
  2. HEAD:获取资源的元信息
  3. POST:向资源提交数据,相当于写入或上传数据
  4. PUT:类似 POST
  5. DELETE:删除资源
  6. CONNECT:建立特殊的连接隧道
  7. OPTIONS:列出可对资源实行的方法
  8. TRACE:追踪请求 - 响应的传输路径

状态码和原因字符串

  • 1xx:提示信息,表示目前是协议处理的中间状态,还需要后续的操作
    • 101 Switching Protocols:它的意思是客户端使用 Upgrade 头字段,要求在 HTTP 协议的基础上改成其他的协议继续通信,比如 WebSocket.而如果服务器也同意变更协议,就会发送状态码 101
  • 2xx:成功,报文已经收到并被正确处理
    • 200 OK
    • 204 No Content
    • 206 Partial Content:”是 HTTP 分块下载或断点续传的基础,在客户端发送“范围请求”、要求获取资源的部分数据时出现,它与 200 一样,也是服务器成功处理了请求,但 body 里的数据不是资源的全部,而是其中的一部分
  • 3xx:重定向,资源位置发生变动,需要客户端重新发送请求
    • 301 Moved Permanently:永久重定向
    • 302 Found:请求的资源还在,但需要暂时用另一个 URI 来访问
    • 304 Not Modified:它用于 If-Modified-Since 等条件请求,表示资源未修改,用于缓存控制.它不具有通常的跳转含义,但可以理解成重定向已到缓存的文件(即缓存重定向)
  • 4xx:客户端错误,请求报文有误,服务器无法处理
    • 400 Bad Request:表示请求报文有错误,但没有具体信息
    • 403 Forbidden 有该文件但不允许访问
    • 404 Not Found
    • 405 Method Not Allowed
    • 408 Request Timeout:请求超时
  • 5xx:服务器错误,服务器在处理请求时内部发生了错误
    • 500 Internal Server Error:和400一样是个通用错误状态码,指服务器错误,但不知道具体错误
    • 501 Not Implemented
    • 502 Bad Gateway
    • 503 Service Unavailable:服务器繁忙

GET/HEAD POST/PUT

  • HEAD 方法是轻量级的 GET,用来获取资源的元信息

  • PUT 基本上是 POST 的同义词,多用于更新数据

默认端口

  • HTTP 默认端口是 80
  • HTTPS 默认端口是 443

HTTP 的优点

  • 简单、灵活、易于扩展(最大优点)
  • 应用广泛、环境成熟
  • 无状态
    • 好处:不需要额外资源来记录状态信息,能够减轻服务器的负担
    • 坏处:例如电商,后台管理等进行操作都需要用户的信息获得对应权限,而无状态就需要每次重新获取(但是可以使用 Cookie 技术来实现有状态)
  • 明文
    • 优点:报文不使用二进制数据,而是简单可阅读的文本形式,方便调试
    • 缺点:心怀不轨的人很容易获取报文
  • 性能:不算差、但也不够好

当然后面的 HTTP/2 和 HTTP/3 就是针对默认 HTTP/1.1 缺点的解决方案

HTTP 里的数据类型和语言类型

  1. 数据类型表示实体数据的内容是什么,使用的是 MIME type,相关的头字段是 AcceptContent-Type
  2. 数据编码表示实体数据的压缩方式,相关的头字段是 Accept-EncodingContent-Encoding
  3. 语言类型表示实体数据的自然语言,相关的头字段是 Accept-LanguageContent-Language
  4. 字符集表示实体数据的编码方式,相关的头字段是 Accept-CharsetContent-Type
  5. 客户端需要在请求头里使用 Accept 等头字段与服务器进行内容协商,要求服务器返回最合适的数据
  6. Accept 等头字段可以用,顺序列出多个可能的选项,还可以用;q=参数来精确指定权重

HTTP 传输大文件

  1. 压缩 HTML 等文本文件是传输大文件最基本的方法;
  2. 分块传输可以流式收发数据,节约内存和带宽,使用响应头字段Transfer-Encoding: chunked来表示,分块的格式是 16 进制长度头 + 数据块
  3. 范围请求可以只获取部分数据,即分块请求,实现视频拖拽或者断点续传,使用请求头字段Range和响应头字段Content-Range,响应状态码必须是 206
  4. 也可以一次请求多个范围,这时候响应报文的数据类型是multipart/byteranges,body 里的多个部分会用 boundary 字符串分隔

Tips:Nginxgzip on指令很智能,只会压缩文本数据,不会压缩图片、音频、视频

短连接/长连接

HTTP 0.9/1.0 时期,底层数据传输基于 TCP/IP,每次发送请求前需要先与服务器建立连接,收到响应包问候会立即关闭连接.因此客户端与服务器的整个连接过程很短暂,所以被称为短连接.所以早期 HTTP 协议也被称为是无连接的协议

短连接的缺点很严重,因为 TCP 协议下,每次建立连接和关闭连接分别需要三次握手,发送三个数据包,需要一个 RTT;关闭连接是四次挥手,四个数据包需要两个 RTT

针对短连接暴露出的缺点,HTTP 协议就提出了长连接的通信方式,也叫持久连接(persistent connections)、连接保活(keep alive)、连接复用(connection reuse);整体采用成本均摊的思路

HTTP/1.1 中默认开启长连接:使用Connection字段,值为keep-alive

在客户端,可以在请求头里加上Connection: close字段,告诉服务器:“这次通信后就关闭连接”。服务器看到这个字段,就知道客户端要主动关闭连接,于是在响应报文里也加上这个字段,发送之后就调用 Socket API 关闭 TCP 连接

  • 服务器端通常不会主动关闭连接,但也可以使用一些策略。拿 Nginx 来举例,它有两种方式:

    1. 使用keepalive_timeout指令,设置长连接的超时时间,如果在一段时间内连接上没有任何数据收发就主动断开连接,避免空闲连接占用系统资源
    2. 使用keepalive_requests指令,设置长连接上可发送的最大请求次数。比如设置成 1000,那么当 Nginx 在这个连接上处理了 1000 个请求后,也会主动断开连接

队头阻塞

队头阻塞与短连接和长连接无关,而是由 HTTP 基本的请求 - 应答模型所导致的。

因为 HTTP 规定报文必须是一发一收,这就形成了一个先进先出的串行队列。队列里的请求没有轻重缓急的优先级,只有入队的先后顺序,排在最前面的请求被最优先处理

如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本

优化方案

  1. 并发连接(concurrent connections),也就是同时对一个域名发起多个长连接,用数量来解决质量的问题
  2. 域名分片(domain sharding)技术,还是用数量来解决质量的思路

图中可以看出,Cookie 是由浏览器负责存储的,而不是操作系统.

Cookie 的有效期可以使用 Expires(过期时间)Max-Age(相对时间) 两个属性来设置

ExpiresMax-Age 可以同时出现,两者的失效时间可以一致,也可以不一致,但浏览器会优先采用 Max-Age 计算失效期

作用域

DomainPath制定了 Cookie 所属的域名和路径,这也是设置 Cookie 的作用域

安全 🔐

JS 可以通过 domani.cookie 来读写 Cookie 数据,这就会有安全隐患,可能导致XSS(跨站脚本攻击)

  • 解决办法:
    • 属性HttpOnly会告诉浏览器,此 Cookie 只能通过浏览器 HTTP 协议传输,禁止其他方式访问,浏览器的 JS 引擎就会禁用 document.cookie 等一切相关的 API,脚本攻击也就无从谈起了
    • 属性SameSite可以防范XSRF(跨站请求伪造)攻击,设置成SameSite=Strict可以严格限定 Cookie 不能随着跳转链接跨站发送,而SameSite=Lax则略宽松一点,允许 GET/HEAD 等安全方法,但禁止 POST 跨站发送
    • 属性Secure,表示这个 Cookie 仅能用 HTTPS 协议加密传输,明文的 HTTP 协议会禁止发送.但 Cookie 本身不是加密的,浏览器里还是以明文的形式存在

用途

  • Cookie 最基本的一个用途就是身份识别,保存用户的登录信息,实现会话事务
  • Cookie 的另一个常见用途是广告跟踪

HTTP 的缓存控制

服务器的缓存控制

服务器标记资源有效期使用的头字段是Cache-Control,里面的值max-age=30就是资源的有效时间,相当于告诉浏览器,“这个页面只能缓存 30 秒,之后就算是过期,不能用”

max-age是 HTTP 缓存控制最常用的属性,此外在响应报文里还可以用其他的属性来更精确地指示浏览器应该如何使用缓存:

  • no_store:不允许缓存,用于某些变化非常频繁的数据,例如秒杀页面;
  • no_cache:它的字面含义容易与 no_store 搞混,实际的意思并不是不允许缓存,而是可以缓存,但在使用之前必须要去服务器验证是否过期,是否有最新的版本
  • must-revalidate:又是一个和 no_cache 相似的词,它的意思是如果缓存不过期就可以继续使用,但过期了如果还想用就必须去服务器验证

客户端的缓存控制

当你点“刷新”按钮的时候,浏览器会在请求头里加一个Cache-Control: max-age=0

Ctrl+F5 的强制刷新又是什么样的呢:它其实是发了一个Cache-Control: no-cache,含义和max-age=0基本一样,就看后台的服务器怎么理解,通常两者的效果是相同的

总结

  1. 缓存是优化系统性能的重要手段,HTTP 传输的每一个环节中都可以有缓存
  2. 服务器使用Cache-Control设置缓存策略,常用的是max-age,表示资源的有效期
  3. 浏览器收到数据就会存入缓存,如果没过期就可以直接使用,过期就要去服务器验证是否仍然可用
  4. 验证资源是否失效需要使用条件请求,常用的是if-Modified-SinceIf-None-Match,收到 304 就可以复用缓存里的资源
  5. 验证资源是否被修改的条件有两个:Last-modifiedETag,需要服务器预先在响应报文里设置,搭配条件请求使用
  6. 浏览器也可以发送Cache-Control字段,使用max-age=0no_cache刷新数据。