你在浏览器输入一个网址,按下回车,几毫秒后页面就出来了。
但在这背后,你的浏览器和服务器之间交换了一封格式严谨的“挂号信”——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(方法 + 路径 + 版本)

  • 头部HostUser-AgentAccept 等键值对

  • 空行:固定一个空行(\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-TypeContent-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?”

更多推荐