HTTP 报文与状态码:你的 SpringBoot 接口每次返回,都在写一封“挂号信”
你在浏览器输入一个网址,按下回车,几毫秒后页面就出来了。
但在这背后,你的浏览器和服务器之间交换了一封格式严谨的“挂号信”——HTTP 报文。
请求报文里写着“我要什么”,响应报文里写着“结果如何”——还有三个数字的状态码,比如200(一切正常)、404(没找到)、500(服务器炸了)。
你在 SpringBoot 里写的@RestController和ResponseEntity,本质上就是在定制这封回信。
大家好,我是 Evan,一个用 curl -v 抓过自己接口返回 500 的 Java+AI 学生。
今天,我从 HTTP 报文的结构讲起,带你拆解请求行、状态行、头部和主体,然后把最常见的状态码(200、301、404、500 等)对应到 SpringBoot 开发中的真实场景。
最后用 curl -v 亲眼看看报文长什么样。
读完这篇,你再看浏览器的开发者工具,会像看家书一样亲切。

📌 写在前面
大二学计网,老师讲 HTTP 报文格式时,我抄下了“请求行、头部、空行、主体”,但总觉得那是理论。
直到我在知识汇项目中写一个文件上传接口,前端说“返回 500 了”,我打开 curl -v,看到响应行是 HTTP/1.1 500 Internal Server Error,紧接着日志里打印了空指针异常。
那一刻我才明白:状态码就是服务器和前端之间的“接头暗号”。
这篇博客,我们就一起把这套暗号彻底搞懂。
一、HTTP 报文长什么样?
HTTP 报文分为请求报文(客户端→服务器)和响应报文(服务器→客户端)。
它们都有通用的结构:起始行 + 头部字段 + 空行 + 可选主体。
1.1 请求报文
GET /user/123 HTTP/1.1
Host: www.example.com
User-Agent: curl/7.68.0
Accept: application/json
-
请求行:
GET /user/123 HTTP/1.1(方法 + 路径 + 版本) -
头部:
Host、User-Agent、Accept等键值对 -
空行:固定一个空行(
\r\n\r\n) -
主体:GET 通常无主体;POST/PUT 有,比如
{"name":"Evan"}
1.2 响应报文
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 27
{"id":123,"name":"Evan"}
-
状态行:
HTTP/1.1 200 OK(版本 + 状态码 + 短语) -
头部:
Content-Type、Content-Length等 -
空行
-
主体:JSON、HTML 或二进制数据

二、状态码分类:数字背后的“潜台词”
HTTP 状态码由三位数字组成,首位定义了类别:

2.1 200 OK——一切正常
-
含义:请求成功,响应体包含所需数据。
-
SpringBoot 默认:
@GetMapping方法正常返回,自动包装为 200。
2.2 301 Moved Permanently——永久搬家
-
含义:资源永久移动到新 URL,浏览器会缓存新地址。
-
场景:网站域名变更、HTTPS 强制跳转。
-
SpringBoot 返回:
return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY)
.header(HttpHeaders.LOCATION, "https://new.example.com")
.build();
2.3 404 Not Found——找不到资源
-
含义:服务器没有找到请求的 URL。
-
常见原因:路径写错、资源不存在。
-
SpringBoot 处理:
@GetMapping("/user/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = service.findById(id);
if (user == null) {
return ResponseEntity.notFound().build(); // 返回 404
}
return ResponseEntity.ok(user);
}
2.4 500 Internal Server Error——服务器内部错误
-
含义:服务器处理请求时发生了未预期的异常(空指针、数据库连接失败等)。
-
SpringBoot 默认:未捕获的异常会返回 500,同时打印堆栈到日志。
-
自定义异常处理:用
@ControllerAdvice返回更友好的错误体。
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleAll(Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("服务器出错了:" + e.getMessage());
}
、SpringBoot 中如何操控状态码和响应?
3.1 @ResponseStatus 简化返回
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {}
当抛出 UserNotFoundException 时,Spring 自动返回 404。
3.2 ResponseEntity 精细控制
@PostMapping("/user")
public ResponseEntity<User> createUser(@RequestBody User user) {
User saved = service.save(user);
URI location = URI.create("/user/" + saved.getId());
return ResponseEntity.created(location).body(saved); // 201 Created
}
-
created(location)返回 201 +Location头。 -
ok()返回 200。 -
badRequest()返回 400。 -
status(HttpStatus.XXX)自定义。
3.3 RestTemplate 的异常处理
try {
ResponseEntity<User> response = restTemplate.getForEntity(url, User.class);
if (response.getStatusCode().is2xxSuccessful()) {
return response.getBody();
}
} catch (HttpClientErrorException.NotFound e) {
// 处理 404
} catch (HttpServerErrorException.InternalServerError e) {
// 处理 500
}
更好的方式是使用 ErrorHandler。
四、用 curl -v 亲眼看看 HTTP 报文
curl -v 会打印完整的请求和响应报文(包括头部)。
curl -v http://localhost:8080/user/123
输出示例:
* Connected to localhost (127.0.0.1) port 8080
> GET /user/123 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 27
<
{"id":123,"name":"Evan"}
-
>开头的行是请求报文。 -
<开头的行是响应报文。 -
空行和主体清晰可见。
抓 POST 请求并查看主体:
curl -v -X POST http://localhost:8080/user -H "Content-Type: application/json" -d '{"name":"Evan"}'
五、常见状态码实战场景

📝 总结

核心结论:
-
HTTP 报文是 Web 通信的“通用语言”,状态码是其中的“关键字”。
-
作为 Java 后端,用
ResponseEntity精准控制状态码和头部,既是对客户端的尊重,也是良好的 API 设计。 -
curl -v是最好的“报文显微镜”,排错时别忘了它。
🤔 思考题:
你在 SpringBoot 中写了一个 @PostMapping 接口,希望当请求体 JSON 缺少必填字段时返回 400。
如果你不使用 @Valid,而是手动校验并返回 ResponseEntity.badRequest().build(),那么响应报文的状态行和主体分别是什么?
如果前端依然收到 200,可能是什么原因?
欢迎在评论区留下你的答案 —— 下一篇我会聊聊 “DNS 解析流程:你在浏览器输入 www.baidu.com 后,谁帮你找到 IP?”
更多推荐

所有评论(0)