前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • HTTP

    • 基础篇

      • HTTP的前世今生
      • HTTP是什么
      • HTTP世界全览
      • HTTP分层
      • 键入网址到回车发生什么
      • HTTP报文是什么样子的
      • 理解请求方法
      • URI
      • 响应状态码
      • HTTP有哪些特点
      • HTTP优缺点
      • HTTP的实体数据
      • HTTP传输大文件
      • HTTP的连接管理
      • HTTP的重定向
      • HTTP的Cookie机制
      • HTTP的缓存控制
      • HTTP的代理服务
      • HTTP的缓存代理
    • 高级篇

    • 扩展篇

  • 浏览器相关

  • 计算机基础

# HTTP报文是什么样子的

30 秒速记

  • 结构:起始行 + 头部 + 空行 + 正文(可选),每行以 CRLF(\r\n)结尾
  • 请求起始行 = 方法 + 目标 + 版本,响应起始行 = 版本 + 状态码 + 原因短语
  • 头部是 key: value,字段名不分大小写;空行是头部和正文的分界线,没有正文也不能省
  • HTTP/1.1 请求必须带 Host,缺了服务器回 400
  • 头部太大服务器会拒:Nginx 默认单行 8KB,超了回 400 或 431;HTTP/2 起报文变成二进制帧,不再是这种文本格式

HTTP/1.1 报文就是一段有固定格式的文本:第一行说做什么,接着若干行头部,一个空行,然后是正文。 请求的第一行是 GET /users HTTP/1.1 这种方法、目标、版本,响应的第一行是 HTTP/1.1 200 OK。头部一行一个 key: value,最后用一个空行告诉对方「头部结束了」,这个空行就算没有正文也必须有。自己用 socket 拼报文时最容易漏的就是这个空行和 Host,漏了服务器直接回 400。到了 HTTP/2,同样的信息被编成二进制帧,语义不变但格式完全不同。

面试官追问

追问 1用 Node 的 net 模块手拼一个最小的 HTTP 请求,怎么写?
参考回答

require('node:net').connect(80, 'example.com', function () { this.write('GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n') }).on('data', d => console.log(d.toString().split('\r\n')[0])),第一行会打出 HTTP/1.1 200 OK。结尾那个 \r\n\r\n 就是最后一个头部的换行加上空行。

追问 2最后一个头后面忘了空行,直接接 JSON,服务器会怎样?
参考回答

服务器会把 JSON 当成下一行头部来解析,{"a":1} 不是合法的 key: value,一般直接回 400。有的实现会一直等空行到超时,所以表现可能是请求挂起。

追问 3SDK 生成了 trace_id: abc,测试能收到,线上 Nginx 后面却拿不到,为什么?
参考回答

Nginx 默认 underscores_in_headers off,带下划线的头会被悄悄丢掉,测试环境如果没过 Nginx 就看不出来。改成 trace-id 最省事,别指望每一层代理都去开这个配置。

追问 4页面请求被服务器回了 431,是什么情况?
参考回答

431 Request Header Fields Too Large,请求头超限了,最常见的是 Cookie 越攒越大。先在 Network 面板看请求头多大,清理不必要的 Cookie 或者缩小作用域;Node 默认头部上限 16KB,Nginx 由 large_client_header_buffers 控制。

# 报文结构

30 秒速记

  • 请求和响应结构一样:起始行 + 头部(合称「请求头 / 响应头」)+ 空行 + body
  • 头部是 ASCII 文本,肉眼能读;body 可以是任意二进制
  • 空行 = 连续两个 CRLF(字节 0D 0A 0D 0A),解析器靠它从读头切到读正文
  • 正文读多长看 Content-Length 或 Transfer-Encoding: chunked,长度写错会在长连接上串包
  • 协议没规定头部上限,但服务器都有:Nginx 默认单行 8KB,超了拒绝

HTTP 报文就像「大头儿子」:头部是大头,空行是脖子,正文是身体,没身体可以,没脖子不行。 解析器先一行行读头部,碰到连续两个 \r\n 就知道头部结束了,接下来读多少字节正文,看 Content-Length,或者 chunked 分块的结束标记。这里最容易出事的是长度:长连接上一条请求接一条请求,第一条的 Content-Length 写小了,多出来的字节就会被当成下一条请求的开头,引发串包。浏览器 GET 一般只有头没有正文,是正常的。

  • 你也许对 TCP/UDP 的报文格式有所了解,拿 TCP 报文来举例,它在实际要传输的数据之前附加了一个 20 字节的头部数据,存储 TCP 协议必须的额外信息,例如发送方的端口号、接收方的端口号、包序号、标志位等等
  • 有了这个附加的 TCP 头,数据包才能够正确传输,到了目的地后把头部去掉,就可以拿到真正的数据。

HTTP 协议也是与 TCP/UDP 类似,同样也需要在实际传输的数据前附加一些头数据,不过与 TCP/UDP 不同的是,它是一个“纯文本”的协议,所以头数据都是 ASCII 码的文本,可以很容易地用肉眼阅读,不用借助程序解析也能够看懂

HTTP 协议的请求报文和响应报文的结构基本相同,由三大部分组成

  • 起始行(start line):描述请求或响应的基本信息;
  • 头部字段集合(header):使用 key-value 形式更详细地说明报文;
  • 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据

这其中前两部分起始行和头部字段经常又合称为“请求头”或“响应头”,消息正文又称为“实体”,但与“header”对应,很多时候就直接称为“body”。

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

所以,一个完整的 HTTP 报文就像是下图的这个样子,注意在 header 和 body 之间有一个“空行”

说到这里,我不由得想起了一部老动画片《大头儿子和小头爸爸》,你看,HTTP 的报文结构像不像里面的“大头儿子”?

报文里的 header 就是“大头儿子”的“大头”,空行就是他的“脖子”,而后面的 body 部分就是他的身体了。

看一下我们之前用 Wireshark 抓的包吧。

在这个浏览器发出的请求报文里,第一行“GET / HTTP/1.1”就是请求行,而后面的“Host”“Connection”等等都属于 header,报文的最后是一个空白行结束,没有 body。

在很多时候,特别是浏览器发送 GET 请求的时候都是这样,HTTP 报文经常是只有 header 而没 body,相当于只发了一个超级“大头”过来,你可以想象的出来:每时每刻网络上都会有数不清的“大头儿子”在跑来跑去。

不过这个“大头”也不能太大,虽然 HTTP 协议对 header 的大小没有做限制,但各个 Web 服务器都不允许过大的请求头,因为头部太大可能会占用大量的服务器资源,影响运行效率。

原理拆解: 对 HTTP/1.1 而言,接收端先读取起始行,再逐行解析头部;每一行都以 CRLF 结束。当连续出现两个 CRLF,即字节序列 \r\n\r\n 时,表示头部结束。随后是否读取正文、读取多少字节,要结合请求方法、响应状态以及 Content-Length、Transfer-Encoding 等信息判断。因此,空行不只是排版习惯,而是解析器从元数据切换到消息内容的协议边界。

最小验证: 输入是一段带有 Content-Length: 5 的完整请求报文,要验证解析器能够分离起始行、头部和正文,并按声明长度取得 hello。

POST /messages HTTP/1.1
Host: example.com
Content-Type: text/plain; charset=utf-8
Content-Length: 5

hello
@前端进阶之旅: 代码已经复制到剪贴板

第一行说明操作类型和目标,随后三行提供协议元数据;空行之后的 hello 才是正文,其按 UTF-8 编码恰好占 5 字节。若删除空行,接收端可能把 hello 当成格式错误的头部;若把长度写成 4,本条消息只会消费四个正文八位组,剩余字节还可能干扰同一连接上的后续报文解析。

边界与排查: “头部可读”只适用于 HTTP/1.x 的报文表达,正文仍可能是图片、压缩数据等任意字节;HTTP/2 则使用二进制帧传输,不能直接套用逐行观察方式。工程上可在浏览器开发者工具或抓包工具中核对请求头、响应头与载荷,也可检查原始字节中是否存在 0D 0A 0D 0A。遇到 400、431 或连接复用后报文错位,应重点检查头部格式、服务器大小上限和正文长度声明是否一致。

面试官追问

追问 1抓包看到浏览器 GET 只有头部和一个空行,后端说请求被截断了,对吗?
参考回答

不对,GET 本来就没有正文,空行之后就结束了。只要头部完整、以 \r\n\r\n 收尾、也没有 Content-Length,这就是一个完整请求。

追问 2长连接上第一条请求 Content-Length: 4,实际正文 5 字节,会发生什么?
参考回答

服务器只读 4 个字节当第一条的正文,剩下那 1 个字节留在连接里,被当成第二条请求的开头,第二条请求的起始行就变成了乱码,直接 400。这类长度不一致在代理和源站解析不一致时还会被利用成请求走私攻击。

追问 3不知道正文多长(比如流式输出),怎么发?
参考回答

用 Transfer-Encoding: chunked,每块前面写十六进制长度,最后发一个长度为 0 的块表示结束。Node 里不设 Content-Length 直接多次 res.write(),框架就自动用 chunked;AI 对话的流式响应常见这种写法。

追问 4想写个脚本按空行切分线上 h2 流量的报文,可行吗?
参考回答

不行。HTTP/2

基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • HTTP

    • 基础篇

      • HTTP的前世今生
      • HTTP是什么
      • HTTP世界全览
      • HTTP分层
      • 键入网址到回车发生什么
      • HTTP报文是什么样子的
        • 报文结构
        • 请求行
        • 状态行
        • 头部字段
        • 总结
      • 理解请求方法
      • URI
      • 响应状态码
      • HTTP有哪些特点
      • HTTP优缺点
      • HTTP的实体数据
      • HTTP传输大文件
      • HTTP的连接管理
      • HTTP的重定向
      • HTTP的Cookie机制
      • HTTP的缓存控制
      • HTTP的代理服务
      • HTTP的缓存代理
    • 高级篇

    • 扩展篇

  • 浏览器相关

  • 计算机基础

完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

HTTP报文是什么样子的|HTTP协议篇

# HTTP报文是什么样子的

30 秒速记

  • 结构:起始行 + 头部 + 空行 + 正文(可选),每行以 CRLF(\r\n)结尾
  • 请求起始行 = 方法 + 目标 + 版本,响应起始行 = 版本 + 状态码 + 原因短语
  • 头部是 key: value,字段名不分大小写;空行是头部和正文的分界线,没有正文也不能省
  • HTTP/1.1 请求必须带 Host,缺了服务器回 400
  • 头部太大服务器会拒:Nginx 默认单行 8KB,超了回 400 或 431;HTTP/2 起报文变成二进制帧,不再是这种文本格式

HTTP/1.1 报文就是一段有固定格式的文本:第一行说做什么,接着若干行头部,一个空行,然后是正文。 请求的第一行是 GET /users HTTP/1.1 这种方法、目标、版本,响应的第一行是 HTTP/1.1 200 OK。头部一行一个 key: value,最后用一个空行告诉对方「头部结束了」,这个空行就算没有正文也必须有。自己用 socket 拼报文时最容易漏的就是这个空行和 Host,漏了服务器直接回 400。到了 HTTP/2,同样的信息被编成二进制帧,语义不变但格式完全不同。

面试官追问

追问 1用 Node 的 net 模块手拼一个最小的 HTTP 请求,怎么写?
参考回答

require('node:net').connect(80, 'example.com', function () { this.write('GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n') }).on('data', d => console.log(d.toString().split('\r\n')[0])),第一行会打出 HTTP/1.1 200 OK。结尾那个 \r\n\r\n 就是最后一个头部的换行加上空行。

追问 2最后一个头后面忘了空行,直接接 JSON,服务器会怎样?
参考回答

服务器会把 JSON 当成下一行头部来解析,{"a":1} 不是合法的 key: value,一般直接回 400。有的实现会一直等空行到超时,所以表现可能是请求挂起。

追问 3SDK 生成了 trace_id: abc,测试能收到,线上 Nginx 后面却拿不到,为什么?
参考回答

Nginx 默认 underscores_in_headers off,带下划线的头会被悄悄丢掉,测试环境如果没过 Nginx 就看不出来。改成 trace-id 最省事,别指望每一层代理都去开这个配置。

追问 4页面请求被服务器回了 431,是什么情况?
参考回答

431 Request Header Fields Too Large,请求头超限了,最常见的是 Cookie 越攒越大。先在 Network 面板看请求头多大,清理不必要的 Cookie 或者缩小作用域;Node 默认头部上限 16KB,Nginx 由 large_client_header_buffers 控制。

# 报文结构

30 秒速记

  • 请求和响应结构一样:起始行 + 头部(合称「请求头 / 响应头」)+ 空行 + body
  • 头部是 ASCII 文本,肉眼能读;body 可以是任意二进制
  • 空行 = 连续两个 CRLF(字节 0D 0A 0D 0A),解析器靠它从读头切到读正文
  • 正文读多长看 Content-Length 或 Transfer-Encoding: chunked,长度写错会在长连接上串包
  • 协议没规定头部上限,但服务器都有:Nginx 默认单行 8KB,超了拒绝

HTTP 报文就像「大头儿子」:头部是大头,空行是脖子,正文是身体,没身体可以,没脖子不行。 解析器先一行行读头部,碰到连续两个 \r\n 就知道头部结束了,接下来读多少字节正文,看 Content-Length,或者 chunked 分块的结束标记。这里最容易出事的是长度:长连接上一条请求接一条请求,第一条的 Content-Length 写小了,多出来的字节就会被当成下一条请求的开头,引发串包。浏览器 GET 一般只有头没有正文,是正常的。

  • 你也许对 TCP/UDP 的报文格式有所了解,拿 TCP 报文来举例,它在实际要传输的数据之前附加了一个 20 字节的头部数据,存储 TCP 协议必须的额外信息,例如发送方的端口号、接收方的端口号、包序号、标志位等等
  • 有了这个附加的 TCP 头,数据包才能够正确传输,到了目的地后把头部去掉,就可以拿到真正的数据。

HTTP 协议也是与 TCP/UDP 类似,同样也需要在实际传输的数据前附加一些头数据,不过与 TCP/UDP 不同的是,它是一个“纯文本”的协议,所以头数据都是 ASCII 码的文本,可以很容易地用肉眼阅读,不用借助程序解析也能够看懂

HTTP 协议的请求报文和响应报文的结构基本相同,由三大部分组成

  • 起始行(start line):描述请求或响应的基本信息;
  • 头部字段集合(header):使用 key-value 形式更详细地说明报文;
  • 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据

这其中前两部分起始行和头部字段经常又合称为“请求头”或“响应头”,消息正文又称为“实体”,但与“header”对应,很多时候就直接称为“body”。

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

所以,一个完整的 HTTP 报文就像是下图的这个样子,注意在 header 和 body 之间有一个“空行”

说到这里,我不由得想起了一部老动画片《大头儿子和小头爸爸》,你看,HTTP 的报文结构像不像里面的“大头儿子”?

报文里的 header 就是“大头儿子”的“大头”,空行就是他的“脖子”,而后面的 body 部分就是他的身体了。

看一下我们之前用 Wireshark 抓的包吧。

在这个浏览器发出的请求报文里,第一行“GET / HTTP/1.1”就是请求行,而后面的“Host”“Connection”等等都属于 header,报文的最后是一个空白行结束,没有 body。

在很多时候,特别是浏览器发送 GET 请求的时候都是这样,HTTP 报文经常是只有 header 而没 body,相当于只发了一个超级“大头”过来,你可以想象的出来:每时每刻网络上都会有数不清的“大头儿子”在跑来跑去。

不过这个“大头”也不能太大,虽然 HTTP 协议对 header 的大小没有做限制,但各个 Web 服务器都不允许过大的请求头,因为头部太大可能会占用大量的服务器资源,影响运行效率。

原理拆解: 对 HTTP/1.1 而言,接收端先读取起始行,再逐行解析头部;每一行都以 CRLF 结束。当连续出现两个 CRLF,即字节序列 \r\n\r\n 时,表示头部结束。随后是否读取正文、读取多少字节,要结合请求方法、响应状态以及 Content-Length、Transfer-Encoding 等信息判断。因此,空行不只是排版习惯,而是解析器从元数据切换到消息内容的协议边界。

最小验证: 输入是一段带有 Content-Length: 5 的完整请求报文,要验证解析器能够分离起始行、头部和正文,并按声明长度取得 hello。

POST /messages HTTP/1.1
Host: example.com
Content-Type: text/plain; charset=utf-8
Content-Length: 5

hello
@前端进阶之旅: 代码已经复制到剪贴板

第一行说明操作类型和目标,随后三行提供协议元数据;空行之后的 hello 才是正文,其按 UTF-8 编码恰好占 5 字节。若删除空行,接收端可能把 hello 当成格式错误的头部;若把长度写成 4,本条消息只会消费四个正文八位组,剩余字节还可能干扰同一连接上的后续报文解析。

边界与排查: “头部可读”只适用于 HTTP/1.x 的报文表达,正文仍可能是图片、压缩数据等任意字节;HTTP/2 则使用二进制帧传输,不能直接套用逐行观察方式。工程上可在浏览器开发者工具或抓包工具中核对请求头、响应头与载荷,也可检查原始字节中是否存在 0D 0A 0D 0A。遇到 400、431 或连接复用后报文错位,应重点检查头部格式、服务器大小上限和正文长度声明是否一致。

面试官追问

追问 1抓包看到浏览器 GET 只有头部和一个空行,后端说请求被截断了,对吗?
参考回答

不对,GET 本来就没有正文,空行之后就结束了。只要头部完整、以 \r\n\r\n 收尾、也没有 Content-Length,这就是一个完整请求。

追问 2长连接上第一条请求 Content-Length: 4,实际正文 5 字节,会发生什么?
参考回答

服务器只读 4 个字节当第一条的正文,剩下那 1 个字节留在连接里,被当成第二条请求的开头,第二条请求的起始行就变成了乱码,直接 400。这类长度不一致在代理和源站解析不一致时还会被利用成请求走私攻击。

追问 3不知道正文多长(比如流式输出),怎么发?
参考回答

用 Transfer-Encoding: chunked,每块前面写十六进制长度,最后发一个长度为 0 的块表示结束。Node 里不设 Content-Length 直接多次 res.write(),框架就自动用 chunked;AI 对话的流式响应常见这种写法。

追问 4想写个脚本按空行切分线上 h2 流量的报文,可行吗?
参考回答

不行。HTTP/2 在线路上是二进制帧,头部经过 HPACK 压缩,根本没有 \r\n 可找。要么用 Wireshark 配合 SSLKEYLOGFILE 解码,要么在应用层拿已经解析好的请求对象。

# 请求行

30 秒速记

  • 请求行 = 方法 + 请求目标 + 版本,空格分隔,CRLF 结尾,例:GET /users?page=2 HTTP/1.1
  • 方法区分大小写,标准方法都写大写,get 不等于 GET
  • 请求目标通常是路径 + 查询串(origin-form),主机在 Host 头里,不在请求行
  • 特殊形式:发给代理用完整 URL(absolute-form),CONNECT 用 host:port,OPTIONS * 用星号
  • 排查编码问题以抓到的原始请求行为准,地址栏显示的是浏览器美化过的

请求行就是请求报文的第一行,一句话说清「用什么方法、对哪个资源、按哪个版本」。 比如 GET /users?page=2 HTTP/1.1,三段用空格隔开。很多人以为查询参数在头部,其实路径和问号后面的参数都在请求目标里;域名不在这一行,在 Host 头里。不过发给正向代理时,请求目标会写完整的 http://example.com/users,因为代理要知道你去哪。方法名区分大小写,规范写法都是大写。

← 键入网址到回车发生什么理解请求方法 →

fe