HTTP 请求协议详解
Spring MVC 处理的是 HTTP 请求,理解 HTTP 请求协议的结构,才能搞清楚 @RequestParam、@RequestBody、@RequestHeader 这些注解到底在取哪部分数据。
一、HTTP 请求报文的结构
一个 HTTP 请求由四部分组成:
请求行
请求头
空行
请求体
用文本表示:
POST /api/users HTTP/1.1 ← 请求行
Host: example.com ← 请求头开始
Content-Type: application/json
Content-Length: 45
User-Agent: Mozilla/5.0
← 空行
{"name":"张三","email":"z@example.com"} ← 请求体
空行是请求头和请求体的分隔符。没有请求体时,空行之后直接结束。
二、请求行
请求行包含三部分:
GET /api/users/1?status=active HTTP/1.1
| 部分 | 示例 | 说明 |
|---|---|---|
| 请求方法 | GET |
HTTP 方法 |
| 请求目标 | /api/users/1?status=active |
路径 + 查询参数 |
| 协议版本 | HTTP/1.1 |
HTTP 版本 |
请求目标中的路径和查询参数
/api/users/1?status=active&page=2
/api/users/1是路径部分,Spring MVC 用@PathVariable获取路径变量。?status=active&page=2是查询字符串,Spring MVC 用@RequestParam获取。
java
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id,
@RequestParam String status,
@RequestParam(defaultValue = "1") int page) { }
三、请求头
请求头是键值对,描述请求的元信息。常见的请求头:
| 请求头 | 作用 | 示例 |
|---|---|---|
Host |
目标主机 | Host: example.com |
Content-Type |
请求体的媒体类型 | Content-Type: application/json |
Content-Length |
请求体长度(字节) | Content-Length: 45 |
Accept |
客户端能接受的响应类型 | Accept: application/json |
User-Agent |
客户端信息 | User-Agent: Mozilla/5.0 |
Cookie |
客户端携带的 Cookie | Cookie: sessionId=abc123 |
Authorization |
认证信息 | Authorization: Bearer xxx |
Referer |
请求来源页面 | Referer: http://example.com/list |
Spring MVC 获取请求头:
java
@GetMapping("/info")
public String info(@RequestHeader("User-Agent") String userAgent,
@RequestHeader(value = "X-Token", required = false) String token) {
return userAgent;
}
获取 Cookie:
java
@GetMapping("/cookie")
public String cookie(@CookieValue("sessionId") String sessionId) {
return sessionId;
}
四、请求体
请求体携带客户端提交的数据。GET 请求通常没有请求体,POST、PUT、PATCH 通常有。
请求体的格式由 Content-Type 决定,常见三种:
1. application/x-www-form-urlencoded
HTML 表单默认提交方式。请求体是键值对,用 & 分隔:
Content-Type: application/x-www-form-urlencoded
name=张三&email=zhangsan@example.com
Spring MVC 获取:
java
@PostMapping("/form")
public String form(@RequestParam String name,
@RequestParam String email) { }
或者用对象接收:
java
@PostMapping("/form")
public String form(User user) { }
2. multipart/form-data
文件上传使用的格式。请求体被分成多个部分,每部分有自己的头部:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="name"
张三
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="a.txt"
Content-Type: text/plain
文件内容
------WebKitFormBoundary--
Spring MVC 获取:
java
@PostMapping("/upload")
public String upload(@RequestParam String name,
@RequestParam MultipartFile file) { }
3. application/json
前后端分离项目最常用的格式。请求体是 JSON 字符串:
Content-Type: application/json
{"name":"张三","email":"zhangsan@example.com"}
Spring MVC 获取:
java
@PostMapping("/json")
public User json(@RequestBody User user) { }
@RequestBody 会读取整个请求体,通过 HttpMessageConverter 反序列化成对象。
五、四种参数获取方式对应关系
| 数据位置 | 示例 | Spring MVC 注解 |
|---|---|---|
| URL 路径中的变量 | /users/1 中的 1 |
@PathVariable |
| URL 查询参数 | ?status=active |
@RequestParam |
| 请求头 | User-Agent: xxx |
@RequestHeader |
| Cookie | Cookie: sessionId=abc |
@CookieValue |
| 请求体(JSON) | {"name":"张三"} |
@RequestBody |
| 请求体(表单) | name=张三&email=... |
@RequestParam 或对象接收 |
六、GET 和 POST 在协议层面的区别
| 对比 | GET | POST |
|---|---|---|
| 参数位置 | URL 查询字符串 | 请求体 |
| 是否有请求体 | 通常没有 | 通常有 |
| Content-Length | 通常为 0 | 有值 |
| 幂等 | 是 | 否 |
| 缓存 | 可缓存 | 默认不缓存 |
GET 请求也可以有请求体,但 HTTP 规范不推荐,很多服务器和客户端会忽略。Spring MVC 的 @RequestBody 在 GET 请求中通常拿不到数据。
七、完整请求示例
1. 查询请求
http
GET /api/users?status=active&page=1 HTTP/1.1
Host: example.com
Accept: application/json
User-Agent: Mozilla/5.0
没有请求体。
2. JSON 提交请求
http
POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 52
Accept: application/json
{"name":"张三","email":"zhangsan@example.com"}
3. 表单提交请求
http
POST /api/login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
username=admin&password=123456
4. 文件上传请求
http
POST /api/upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----Boundary
------Boundary
Content-Disposition: form-data; name="file"; filename="a.txt"
Content-Type: text/plain
文件内容
------Boundary--
八、Spring MVC 中的完整接收示例
java
@RestController
@RequestMapping("/api/users")
public class UserController {
// 路径变量 + 查询参数
@GetMapping("/{id}")
public User getById(@PathVariable Long id,
@RequestParam(required = false) String fields) {
return userService.getById(id);
}
// JSON 请求体
@PostMapping
public User create(@RequestBody @Valid User user) {
return userService.save(user);
}
// 表单提交
@PostMapping("/form")
public User createByForm(User user) {
return userService.save(user);
}
// 请求头
@GetMapping("/header")
public String header(@RequestHeader("User-Agent") String ua) {
return ua;
}
// Cookie
@GetMapping("/cookie")
public String cookie(@CookieValue("sessionId") String sessionId) {
return sessionId;
}
// 文件上传
@PostMapping("/upload")
public String upload(@RequestParam MultipartFile file) {
return file.getOriginalFilename();
}
}
九、常见问题
1. 415 Unsupported Media Type
请求的 Content-Type 与接口的 consumes 不匹配。比如接口声明 consumes = "application/json",但客户端发了 text/plain。
2. 400 Bad Request
请求体 JSON 格式错误,或参数类型不匹配。检查 Content-Type 是否为 application/json,JSON 是否合法。
3. @RequestBody 拿不到数据
检查请求方法是否为 POST/PUT/PATCH,Content-Type 是否为 application/json。GET 请求通常没有请求体。
4. @RequestParam 拿不到表单数据
检查 Content-Type 是否为 application/x-www-form-urlencoded。如果是 application/json,表单参数拿不到,需要用 @RequestBody。
5. 中文乱码
检查请求头 Content-Type 是否带了 charset=UTF-8,服务端编码配置是否正确。
十、总结
| 请求组成部分 | 内容 | Spring MVC 获取方式 |
|---|---|---|
| 请求行 | 方法、路径、查询参数 | @PathVariable、@RequestParam |
| 请求头 | 元信息、认证、Cookie | @RequestHeader、@CookieValue |
| 空行 | 分隔符 | --- |
| 请求体 | 表单、JSON、文件 | @RequestBody、@RequestParam、MultipartFile |
| Content-Type | 请求体格式 | 获取方式 |
|---|---|---|
application/x-www-form-urlencoded |
键值对 | @RequestParam 或对象 |
multipart/form-data |
分段数据 | MultipartFile |
application/json |
JSON 字符串 | @RequestBody |
HTTP 请求协议的核心是"请求行 + 请求头 + 空行 + 请求体"四部分。路径和查询参数在请求行,元信息在请求头,业务数据在请求体。Spring MVC 的注解就是按这四部分设计的:@PathVariable 取路径,@RequestParam 取查询参数和表单,@RequestHeader 取请求头,@RequestBody 取请求体。把请求结构和注解对应起来,参数绑定就不会搞混。