跳转至

应用层 - HTTP

HTTP 是什么

HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等 超文本数据 的约定和规范。

HTML 是超文本的载体,使用各种标签描述文字、图片、超链接等资源

HTTP结构介绍

HTTP 报文结构

由 3 大部分组成:

  1. 起始行 (start line):描述请求或相应的基本信息
  2. 头部字段 (header):使用 key - value 的形式详细说明报文
  3. 消息正文 (entity):实际传输的数据,可以是图片,文本,视频等

HTTP 协议规定报文必须有 header,但可以没有 body,而且在 header 之后必须要有一个“空行”,也就是“CRLF”,十六进制的“0D0A”

image-20241220155214904

请求

请求行

request line,也即 请求报文 的起始行,描述客户端想要如何操作服务器资源

由三部分构成:

image-20241220155551783

  1. 请求方法:如 GET / POST
  2. 请求目标:通常是一个 URI
  3. 版本号:报文使用的 HTTP 协议版本

使用 space 分割,cltf 换行结束,例如:

GET / HTTP/1.1

GET 为请求方法,/ 为请求目标,HTTP/1.1 是版本号:需要获取网站根目录下的文件

请求头

请求行 / 状态行 + 头部字段集合 就构成了完整的 请求头 / 响应头

image-20241220160917077

请求头 和 响应头 的结构基本一致,唯一的区别就是起始行。

HTTP 的头字段非常灵活,可以使用标准里的 Host , Connection 等已有头,也可以任意添加自定义头,这给 HTTP 协议带来了拓展性。

头部字段是 key - value 形式,key 和 value 之间使用 : 分隔,最后用 CLTF 换行表示字段结束,例如:

Host: 127.0.0.1
Host: www.baidu.com

Connection: keep-alive

User-Agent: 浏览器版本
Accept: 浏览器可以接受的资源类型,*/* 表示所有

请求体

POST 请求的最后一个部分,和头字段使用一个空行隔开

  • Get 没有请求体(在请求的资源路径会放参数,请求参数长度有限制)
  • Post 参数放在请求体中

响应

状态行

status line,即 响应报文 的起始行,为服务器响应的状态

由三部分构成:

image-20241220160322875

  1. 版本号:表示 HTTP 协议版本
  2. 状态码:三位十进制数,如 200 表示成功,500 是服务器错误
  3. 原因:作为数字状态码的补充,是更详细的解释文字

例如:

HTTP/1.1 200 OK

HTTP/1.1 404 NOt Found

响应头

从第二行开始,格式为 KV 结构

image-20241220160931405

响应体

存放响应数据(浏览器通过解析这个数据渲染页面)

HTTP/1.1 200 OK
Server: Tengine
Content-Type: text/html
Transfer-Encoding: chunked

<html>
<head>
<title></title>
</head>
<body></body>
</html>

拆分请求行字段

HTTP 提供了哪些请求方法

标准请求方法

HTTP/1.1 中规定了 8 种方法,单词都需要是大写形式:

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

image-20241220162402327

服务器对于请求有绝对的决策能力:

比如,你发起了一个 GET 请求,想获取“/orders”这个文件,但这个文件保密级别比较高,服务器就可以有如下的几种响应方式:

  • 假装这个文件不存在,直接返回一个 404 Not found 报文
  • 稍微友好一点,明确告诉你有这个文件,但不允许访问,返回一个 403 Forbidden
  • 再宽松一些,返回 405 Method Not Allowed,然后用 Allow 头告诉你可以用 HEAD 方法获取文件的元信息

GET / HEAD

  • GET 方法请求从服务器里获取资源,搭配 URI 和其他头字段能实现对资源的更精细操作。

    例如,在 URI 后使用“#”,就可以在获取页面后直接定位到某个标签所在的位置;使用 If-Modified-Since 字段就变成了“有条件的请求”,仅当资源被修改时才会执行获取动作;使用 Range 字段就是“范围请求”,只获取资源的一部分数据

  • HEAD 方法也是请求从服务器获取资源,但服务器不会返回请求的实体数据,只会传回响应头,即资源的 meta data

    例如,可以用来检查一个文件是否存在,或者检查文件的最新版本

POST / PUT

POST / PUT 是向 URI 指定的资源提交数据,数据放在报文的 body 里

  • POST 通常表示“新建”

    例如,上论坛敲了一堆字后点击“发帖”按钮,浏览器就执行了一次 POST 请求,把文字放进报文的 body 里,然后拼好 POST 请求头,通过 TCP 协议发给服务器;上购物网站,点击“加入购物车”,这时也会有 POST 请求,浏览器会把商品 ID 发给服务器,服务器再把 ID 写入你的购物车相关的数据库记录

  • PUT 通常表示“修改”

安全与幂等

  • 安全:请求不会对服务器上的资源造成实质性的修改。

    此定义下只有 GET 和 HEAD 方法是安全的

  • 幂等:如果多次执行相同的操作,结果也都是相同的

    GET / HEAD / DELETE / PUT 幂等

    POST 会新建多个资源,不幂等

URI

URI 用于标记(区分)服务器上的资源,其英文为 Uniform Resource Identifier,经常出现在浏览器的地址栏里,但不完全等同于 URL Uniform Resource Locator ,其包含 URL 和 URN 两个部分。HTTP 世界里使用的网址实际上是 URL。

URI 格式

URI 是一个字符串,用于唯一地标记资源的位置或者名字

image-20241220165253301

URI 的基本组成

  1. scheme:协议名,表示资源应该使用哪种协议来访问

    最常见的 scheme 即为 "http",表示使用 HTTP 协议。另外还有 "https" ,表示使用经过加密的安全的 HTTPS 协议。还有 "ftp" 等。

  2. :// :三个固定的分离字符

  3. authority:表示资源所在的主机名,通常形式为 host:port ,即主机名 + 端口号

    • 主机名可以是 IP 地址或域名的形式,对于 HTTP / HTTPS 这样的网络通信协议必须要有
    • 端口号可以省略,浏览器等客户端会根据 scheme 使用默认的端口号

      例如 HTTP 的默认端口号为 80,HTTPS 的默认端口号为 443

  4. path:采用类文件系统目录路径的表示方式,path 部分必须以 "/" 开头

    • http://nginx.org

      省略了端口号(默认为 80),省略了路径(默认为 "/",表示根目录)

    • https://tools.ietf.org/html/rfc7230

      路径为 "/html/rfc7230"

    • file:///D:/http_study/www/

      注意,这里的协议名为 "file",表示本地文件,这是 file 类型的 URI 的特例:允许省略主机名,默认是本机 localhost

客户端URI -> 服务器URI

注意,客户端和服务器看到的 URI 是不一样的。

  • 客户端(比如浏览器)看到的必须是完整的 URI
  • 服务器看到的只是报文请求行里被删除了协议名、主机名以及端口号的 URI

浏览器输入:http://www.chrono.com/11-1

image-20241220171938331

URI 的查询参数

使用 协议名 + 主机名 + 路径 的方式,已经可以精确定位网络上的任何资源了。但是有时还想再操作资源的时候附加一些额外的参数。

此时在 path 之后用一个 ? 开始,但不包含 ? 表示额外的要求。

例如:

http://www.chrono.com:8080/11-1?uid=1234&name=mario&referer=xxx

(Chrome 的开发工具也可以解码出 query 里的 KV 对,如果 key 后面不写值就会被解析成空的 value)

URI 的完整格式

URI 真正的完整形态如下:

image-20241220211058537

这个形态多出了两个部分:

  • 身份信息:在协议名之后,主机名之前,表示登录主机时的身份以及密码(以明文形式暴露了敏感信息,现已不推荐)
  • 片段标识符:为浏览器而非服务端提供的一个”标签”,浏览器可以在获取资源后直接跳转到其指示的位置

URI 的编码

在 URI 里只能使用 ASCII 码,但是可能出现如下情况:

  • 在 URI 里使用汉语等非英语
  • 某些特殊的 URI 可能会在 path / query 里出现 "@" "&" "?" 等起定界作用的字符,会导致 URI 解析错误

解决方法:直接把这些字符转换成十六进制字节值,然后在前面加上 %

例如空格被转义成 "%20" ,将其从地址栏拷贝到其他编辑器中就会现出 % 原型

拆分状态行字段

HTTP 状态码

image-20241220213549272

服务器返回的状态码(status code)是状态行中的第二个字段,是一个十进制数字,客户端可以根据状态码转换处理状态。

状态码的分类

状态码的范围是 100 ~ 599,可以分为 5 类:

  1. 1 开头:提示信息,表示目前是协议处理的中间状态,还需要后续操作

  2. 2 开头:成功,报文已经收到并被正确处理

    • 200 OK :表示一切正常,如果是非 HEAD 请求,通常响应头后有 body 数据
    • 204 No Content :成功,且响应头后没有数据
    • 206 Partial Content :是 HTTP 分块下载 / 断点续传的基础。在客户端发送了范围(查询)请求后,服务器成功处理了请求,但是 body 数据里不是资源的全部而是一部分

      此处通常还伴随着 头字段 Content-Range,例如 "Content-Range: bytes 0-99/2000" ,表示此次获取了 2000 字节中的前 100 字节

  3. 3 开头:重定向,资源位置发生变动,需要客户端重新发送请求

    • 301 Moved Permanently :此次请求的资源不存在,需要新的 URI 访问
    • 302 Moved Tempoarily :请求的资源还在,浏览器会自动重新访问新的页面
    • 304 Not Modified :让客户端访问本地的缓存,减轻服务器的压力
  4. 4 开头:责任在客户端,请求报文有误,服务端无法处理

    • 400 Bad Request :请求报文有错误
    • 403 Forbidden:无权访问此资源
    • 404 Not Found:not found(url 路径错误)
    • 405 Method Not Allowed:请求方式有误,比如应该使用 GET,但是使用了 POST
    • 其他,可以通过 Reason 原因来理解
  5. 5 开头:责任在服务端,服务器在处理请求时内部发生了错误

    • 500 Internel Server Error
    • 502 Bad Gateway :服务器自身工作正常,服务器作为网关或代理返回的错误码
    • 503 Service Unavailable :服务器太忙,无法响应
    • 511 Network Authentication Required :客户端需要进行身份验证才能访问

HTTP 的连接

HTTP 的连接管理

短连接

HTTP 协议底层的数据传输基于 TCP/IP,每次发送请求前需要先与服务器建立连接收到响应报文后会立即关闭连接。整个连接过程很短暂,所以称为“短连接”(short-lived connections)。

注意到三握四挥(1RTT + 2RTT)代价较为昂贵

长连接

基于“分母效应”,在一次建立连接与关闭连接之间持续地处理多次请求

image-20241221215744807

长连接的缺点是会消耗服务器的资源,所以长连接也需要在适当时间主动关闭

连接相关头字段

注意,由于长连接对性能的改善效果非常显著,所以在 HTTP/1.1 中的连接都会默认启用长连接。不需要用什么特殊的 头字段 指定,只要向服务器发送了第一次请求,后续的请求都会重复利用第一次打开的 TCP 连接,也就是长连接,在这个连接上收发数据。

  • 如果需要利用长连接机制,也可以在请求头中明确地要求,使用:

    Connection: keep-alive

  • 如果需要在此次通信后就关闭连接,可以在请求头中加入:

    Connection: close

    此时响应报文中也会加上这个字段

  • 如果需要限定长连接的超时时间,客户端与服务器可以在报文中加入头字段,但是其约束力并不强,双方不一定遵守

    Keep-Alive: timeout=value

不管客户端是否显式要求长连接,支持长连接的服务器总会在响应报文里放一个

Connection: keep-alive

队头阻塞

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

HTTP 报文必须是 一发一收,形成了一个先进先出的穿行队列,如果队首的请求因为处理太慢而耽误时间,则后续请求也需要一起等待

性能优化

对头阻塞问题在 HTTP/1.1 中无法解决,只能缓解。

  • 并发连接:浏览器同时打开多个连接到同一个域名
  • 域名分片:注意到浏览器对每个域名最多只能建立一定数量的并发连接。将资源分布到多个子域名上,每个子域名可以独立建立连接

随着 HTTP/2 和 HTTP/3 的出现,这些问题得到了更好的解决,因为它们支持多路复用,可以在一个连接上并行处理多个请求,从而从根本上减少了队头阻塞的影响。

由服务器创建,浏览器储存,随后的每次请求都会携带 Cookie

image-20241223222709536

xxl job / 定时任务

设计模式相当于一种代码规范