HTTP核心知识
面试为什么反复考 HTTP 基础
框架帮你封装了请求库,Axios 帮你处理了大部分 header,但面试官关心的不是你会不会用 fetch,而是你是否理解一个请求从组装到发出、从响应到解析的每一个环节。状态码选错会让客户端做出错误决策,Content-Type 配错会导致参数解析失败,缓存头不理解就只能靠"清缓存大法"排问题。
这篇文章围绕 HTTP 报文结构、状态码、请求方法、常用头字段、Content-Type 和 RESTful 设计规范展开,覆盖面试中最高频的追问点。
HTTP 报文结构
HTTP 通信的基本单位是报文。请求和响应各有一套固定结构,理解它们是读懂所有后续知识的前提。
请求报文
一个 HTTP 请求报文由四部分组成:
POST /api/users HTTP/1.1 ← 请求行:方法 + 路径 + 协议版本
Host: example.com ← 请求头(多行 key: value)
Content-Type: application/json
Authorization: Bearer eyJhbGci...
{"name": "nanyi", "role": "fe"} ← 请求体(GET 请求通常没有)
- 请求行:包含 HTTP 方法(GET、POST、PUT、DELETE 等)、请求路径(含查询参数)和协议版本。
- 请求头:一系列键值对,用来描述客户端环境、期望的响应格式、认证信息、缓存策略等。
- 空行:标志头部结束、正文开始。
- 请求体:携带要发送给服务端的数据,GET 请求通常没有请求体。
响应报文
HTTP/1.1 200 OK ← 响应行:协议版本 + 状态码 + 状态描述
Content-Type: application/json
Set-Cookie: sid=abc123; HttpOnly
Cache-Control: no-cache
{"id": 1, "name": "nanyi"} ← 响应体
- 响应行(状态行):协议版本、三位数状态码和一段可读描述。
- 响应头:描述响应的元信息,如内容类型、缓存策略、Cookie 设置、跨域授权等。
- 空行 + 响应体:服务端返回的实际数据。
面试追问:请求头和请求体之间靠什么分隔? 答案是一个空行(\r\n\r\n),这也是 HTTP/1.x 文本协议解析的关键分界点。
状态码分类详解
状态码是服务端对请求的"一句话判决"。三位数字,第一位决定类别。
1xx — 信息性
表示请求已被接收,继续处理。实际开发中很少直接接触。
- 100 Continue:客户端发送了带
Expect: 100-continue的请求头,服务端告知"可以继续发送请求体"。常见于上传大文件时,客户端先试探服务端是否愿意接收。
2xx — 成功
| 状态码 | 含义 | 典型场景 |
|---|---|---|
| 200 OK | 请求成功,响应体包含结果 | GET 查询、POST 提交后返回结果 |
| 201 Created | 资源创建成功 | POST 创建用户、文章等 |
| 204 No Content | 成功但无响应体 | DELETE 删除资源、PUT 更新后无需返回内容 |
面试追问:POST 成功后应该返回 200 还是 201? 如果是创建型操作(新增用户、新建订单),语义上应返回 201,且响应头中可以带 Location 指向新资源的地址。如果只是触发一次计算或查询,200 更合适。
3xx — 重定向
| 状态码 | 含义 | 特点 |
|---|---|---|
| 301 Moved Permanently | 永久重定向 | 浏览器会缓存,下次直接访问新地址;搜索引擎会更新索引 |
| 302 Found | 临时重定向 | 浏览器不会缓存,每次仍请求原地址 |
| 304 Not Modified | 资源未修改,使用缓存 | 配合 If-None-Match / If-Modified-Since 使用 |
| 307 Temporary Redirect | 临时重定向,但不允许改变请求方法 | POST 请求被 307 重定向后仍保持 POST |
面试高频追问:
301 和 302 的区别? 核心在于浏览器是否缓存重定向。301 是永久的,浏览器记住后不再访问原 URL;302 是临时的,每次还是先访问原 URL。从 SEO 角度,301 会让搜索引擎把权重转移到新地址。
302 和 307 的区别? 302 在规范上允许浏览器把 POST 请求在重定向时降级为 GET(历史包袱,实际浏览器也确实这么干了),307 则严格禁止改变方法。如果你的 POST 请求被重定向后还需要保持 POST,必须用 307。
304 是怎么工作的? 浏览器第一次请求资源时,服务端返回 ETag 或 Last-Modified。后续请求时浏览器带上 If-None-Match 或 If-Modified-Since,服务端比对后发现资源没变,返回 304 且不附带响应体,浏览器直接用本地缓存。
4xx — 客户端错误
| 状态码 | 含义 | 典型场景 |
|---|---|---|
| 400 Bad Request | 请求格式错误或参数非法 | JSON 格式错误、必填参数缺失、枚举值不合法 |
| 401 Unauthorized | 未认证 | 未携带 token 或 token 过期 |
| 403 Forbidden | 已认证但无权限 | 普通用户访问管理后台接口 |
| 404 Not Found | 资源不存在 | URL 拼错、资源已被删除 |
面试追问:401 和 403 的区别? 401 是"你是谁我不知道"(身份未认证),403 是"我知道你是谁,但你没权限"(认证通过但授权失败)。很多后端错误地在未登录时返回 403,这在语义上是不准确的。
5xx — 服务端错误
| 状态码 | 含义 | 典型场景 |
|---|---|---|
| 500 Internal Server Error | 服务端未预期的错误 | 代码抛异常、空指针、数据库查询失败 |
| 502 Bad Gateway | 网关收到了上游服务的无效响应 | Nginx 转发请求到 Node 服务,Node 进程崩溃 |
| 503 Service Unavailable | 服务暂时不可用 | 服务正在部署、过载限流、依赖的下游服务挂了 |
面试追问:502 和 503 有什么区别? 502 意味着网关层(如 Nginx、API Gateway)确实尝试转发了请求,但上游返回了不合法的响应或连接失败。503 是服务自身告诉你"我现在忙不过来",可能在做滚动发布、触发了限流或健康检查失败被摘流量。
GET vs POST
这可能是面试里被问得最多的 HTTP 题目。表面上只是"参数放 URL 还是 Body",实际涉及语义、安全性、幂等性和缓存策略。
| 维度 | GET | POST |
|---|---|---|
| 语义 | 获取资源 | 提交数据、创建资源、触发操作 |
| 参数位置 | URL 查询字符串(?key=value) | 请求体 |
| URL 长度限制 | 浏览器有限制(Chrome 约 2MB URL,但实际 query string 建议 2KB 以内) | 无实质限制(受服务端配置约束) |
| 幂等性 | 幂等:多次请求结果一致 | 非幂等:多次提交可能创建多条记录 |
| 缓存 | 默认可缓存 | 默认不可缓存 |
| 安全性 | 参数暴露在 URL 中,可被浏览历史、日志、Referer 泄露 | 参数在请求体中,不会出现在 URL 中 |
| 书签/分享 | 可以被收藏为书签 | 无法收藏 |
| 回退行为 | 浏览器回退时不会重新提交 | 浏览器回退时会提示重新提交 |
几个容易踩坑的细节:
POST 比 GET 更安全? 不完全对。POST 只是参数不在 URL 里,但请求体在 HTTP 明文传输中一样能被抓包。真正的安全要靠 HTTPS 加密。POST 的"安全"更多是指参数不会出现在浏览器历史、代理日志和 Referer 头中。
GET 请求可以有 Body 吗? HTTP 规范没有禁止 GET 带 Body,但大多数浏览器、网关和框架不支持或会忽略。ElasticSearch 曾经用 GET + Body 做查询,后来也加了 POST _search 的写法。工程实践中不建议这样做。
幂等性为什么重要? 网络不稳定时客户端可能重试。如果 GET 是幂等的,重试不会有副作用;如果 POST 不做幂等处理,重试可能导致重复下单、重复扣款。这是面试里从"GET vs POST"延伸到"接口幂等设计"的常见路径。
常用请求头
Content-Type
告诉服务端请求体的数据格式,直接决定服务端如何解析参数。
Content-Type: application/json
Content-Type: application/x-www-form-urlencoded
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
这三个值是前端开发中最常用的,后面会单独展开。
Accept
告诉服务端客户端期望的响应格式。服务端可以据此做内容协商(Content Negotiation)。
Accept: application/json
Accept: text/html, application/xhtml+xml
Accept: */*
Authorization
携带认证信息,最常见的是 Bearer Token:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
也有 Basic Auth 的写法(Base64 编码用户名和密码),但现代应用基本都用 Token。
Cookie
浏览器自动携带该域名下的 Cookie:
Cookie: sid=abc123; theme=dark; _ga=GA1.2.12345
每次请求都会带上,这也是为什么不建议在 Cookie 中存放大量数据——它会增大每个请求的体积。
Origin
标识请求的来源(协议 + 域名 + 端口),在跨域请求和 CORS 预检中起关键作用:
Origin: https://blog.example.com
服务端根据 Origin 决定是否允许跨域访问。注意 Origin 和 Referer 的区别:Origin 只有协议+域名+端口,不包含路径;Referer 包含完整 URL 路径。
Referer
标识当前请求是从哪个页面发起的:
Referer: https://blog.example.com/posts/httpBasics
常用于统计来源、防盗链。注意 Referer 是 HTTP 规范中的历史拼写错误(正确应为 Referrer),但约定俗成一直沿用。
User-Agent
描述客户端环境:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
服务端可以据此做设备判断、浏览器兼容或返回不同的页面版本。
常用响应头
Set-Cookie
服务端通过该头设置 Cookie:
Set-Cookie: sid=abc123; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=86400
关键属性:
- HttpOnly:禁止 JavaScript 通过
document.cookie读取,防 XSS 窃取。 - Secure:仅在 HTTPS 下传输。
- SameSite:控制跨站请求是否携带 Cookie,
Lax是现代浏览器的默认值。 - Max-Age / Expires:控制过期时间,不设置则为会话级 Cookie(关闭浏览器即失效)。
Cache-Control
控制缓存策略,是浏览器缓存机制的核心头字段:
Cache-Control: public, max-age=31536000 // 静态资源,缓存一年
Cache-Control: no-cache // 每次使用前必须去服务端验证
Cache-Control: no-store // 完全禁止缓存
面试追问:no-cache 和 no-store 的区别? no-cache 并不是"不缓存",而是"缓存了,但每次使用前要去服务端验证是否过期"(配合 ETag / Last-Modified)。no-store 才是真正的"完全不缓存",任何中间代理和浏览器都不得保存响应。
Access-Control-* 系列(CORS)
跨域资源共享相关的响应头:
Access-Control-Allow-Origin: https://blog.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
- Allow-Origin:允许哪些源访问。设为
*时不能同时设置Allow-Credentials: true。 - Allow-Methods:允许的 HTTP 方法。
- Allow-Headers:允许的自定义请求头。
- Allow-Credentials:是否允许跨域携带 Cookie。
- Max-Age:预检请求(OPTIONS)的结果可以缓存多久,避免每次跨域请求都发两次。
Content-Type(响应)
告诉客户端响应体的数据格式:
Content-Type: application/json; charset=utf-8
Content-Type: text/html; charset=utf-8
Content-Type: application/octet-stream
Location
配合 3xx 重定向使用,指示客户端跳转到的新地址。也可在 201 响应中指向新创建资源的 URL:
HTTP/1.1 301 Moved Permanently
Location: https://new.example.com/posts/httpBasics
Content-Type 三大常见值
前端发请求时,Content-Type 选错是一个高频 bug 来源。
application/json
最常用的格式。请求体是 JSON 字符串:
POST /api/users HTTP/1.1
Content-Type: application/json
{"name": "nanyi", "role": "fe"}
后端通常通过 body-parser 或框架内置中间件解析。Axios 默认使用这个格式。
application/x-www-form-urlencoded
HTML <form> 表单的默认提交格式。参数以 key=value&key=value 的形式编码在请求体中:
POST /api/login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username=nanyi&password=123456
特殊字符会被 URL 编码(空格 → + 或 %20)。jQuery 的 $.ajax 默认使用这个格式,Axios 需要手动用 qs.stringify() 或 URLSearchParams 转换。
multipart/form-data
用于文件上传或混合数据提交。每个字段用 boundary 分隔:
POST /api/upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA
------WebKitFormBoundary7MA
Content-Disposition: form-data; name="file"; filename="avatar.png"
Content-Type: image/png
(二进制数据)
------WebKitFormBoundary7MA
Content-Disposition: form-data; name="userId"
12345
------WebKitFormBoundary7MA--
使用 FormData API 构建请求时,浏览器会自动设置 Content-Type 和 boundary,不要手动设置 Content-Type,否则 boundary 对不上会导致服务端解析失败。
面试追问:为什么文件上传不用 application/json? JSON 是文本格式,二进制文件需要 Base64 编码后才能放进 JSON 字符串里,体积会膨胀约 33%,且解码有额外开销。multipart/form-data 原生支持二进制流传输,更高效。
RESTful API 设计规范
REST(Representational State Transfer)不是协议,是一种 API 设计风格。核心思想是把后端数据抽象成"资源",用 URL 定位资源、用 HTTP 方法表达操作。
资源命名
✅ GET /api/users 获取用户列表
✅ GET /api/users/123 获取 ID 为 123 的用户
✅ POST /api/users 创建用户
✅ PUT /api/users/123 全量更新用户
✅ PATCH /api/users/123 部分更新用户
✅ DELETE /api/users/123 删除用户
❌ GET /api/getUser?id=123
❌ POST /api/deleteUser
❌ POST /api/updateUserName
核心原则:
- URL 中用名词复数表示资源集合(
/users而非/user或/getUsers)。 - 操作语义由 HTTP 方法承担,URL 中不要出现动词。
- 层级关系用路径表示:
/users/123/posts表示用户 123 的文章列表。 - 用查询参数处理过滤、分页和排序:
/users?role=admin&page=2&limit=20。
HTTP 方法语义
| 方法 | 语义 | 幂等 | 安全 | 典型响应 |
|---|---|---|---|---|
| GET | 获取资源 | ✅ | ✅ | 200 |
| POST | 创建资源 | ❌ | ❌ | 201 + Location |
| PUT | 全量替换资源 | ✅ | ❌ | 200 或 204 |
| PATCH | 部分更新资源 | ❌ | ❌ | 200 |
| DELETE | 删除资源 | ✅ | ❌ | 204 |
这里的"安全"指的是该方法是否会修改服务端状态。GET 是安全的(只读),POST/PUT/DELETE 不安全(有副作用)。
"幂等"指多次调用同一接口的效果和一次调用一致。GET 天然幂等,DELETE 也是幂等的(删除一个已不存在的资源,结果还是"不存在"),PUT 是幂等的(全量替换多次结果一致),POST 不幂等(每次调用可能创建新资源)。
状态码使用
RESTful API 应该正确使用状态码,而不是所有接口一律返回 200 然后把真正的错误码塞到响应体里。
// ❌ 不推荐:永远 200,错误信息藏在 body 里
HTTP/1.1 200 OK
{"code": 40401, "message": "user not found"}
// ✅ 推荐:用 HTTP 状态码表达语义
HTTP/1.1 404 Not Found
{"error": "User not found", "detail": "No user with id 999"}
常见对应关系:
- 查询成功 → 200
- 创建成功 → 201
- 更新 / 删除成功且不返回内容 → 204
- 参数校验失败 → 400
- 未认证 → 401
- 无权限 → 403
- 资源不存在 → 404
- 服务端异常 → 500
总结
HTTP 基础知识在面试中是常驻考点,原因是它贯穿了前后端协作的每一个环节。把这些知识串起来:
- 报文结构是理解一切的基础:请求行/头/体、响应行/头/体,空行是分界符。
- 状态码是服务端的语义表达:2xx 成功、3xx 重定向、4xx 客户端错误、5xx 服务端错误。记住常用的十几个,能说清 301 vs 302、304 的协商缓存流程、401 vs 403 的区别。
- GET vs POST 不只是参数位置的差异,背后是语义、幂等性、缓存和安全的综合考量。
- 请求头和响应头是 HTTP 能力的载体:Content-Type 决定解析方式、Authorization 承载认证、Cache-Control 控制缓存、CORS 头处理跨域。
- Content-Type 选错是接口联调中最常见的低级错误,三种格式各有适用场景。
- RESTful 是用 HTTP 语义设计 API 的最佳实践:用 URL 表示资源、用方法表示操作、用状态码表示结果。
面试时不要只背结论。能结合自己项目中遇到的跨域问题、缓存坑或 Content-Type 排查经历来展开,比背八股更有说服力。