1. HTTP 协议
HTTP 协议 是一个应用层协议,它是 Hyper Text Transfer Protocol
(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
HTTP 协议 是一个无状态的请求/响应协议。
请求消息对象 Request
客户端发送一个HTTP请求到服务器的请求消息包括以下格式:
- 请求行
- 请求头部
- 空行
- 请求数据(请求体 body)
如下图:
【注意】:
请求体 body 可以为空,常见的 GET
请求就是这种情况。但当请求体 body 不为空时,接收的一端需要知道它是什么类型的数据,采用什么编码。这时候,就需要在 Content-Type
来指明请求体 body 的媒体格式(MIME)类型。
GET
请求一般(标准)不包含请求体 body,不用设置 Content-Type
,但 POST
请求一般(标准)包含请求体 body,需要设置 Content-Type
。
GET 请求:
GET /user?id=1 HTTP/1.1
Host img.mukewang.com
User-Agent Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Accept image/webp,image/*,*/*;q=0.8
Referer http://www.imooc.com/
Accept-Encoding gzip, deflate, sdch
Accept-Language zh-CN,zh;q=0.8
-
第一部分:请求行,用来说明请求类型、要访问的资源、所使用的HTTP版本。
GET说明请求类型为GET
、user?id=1
为要访问的资源,该行的最后一部分说明使用的是HTTP1.1
版本。 -
第二部分:请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息
从第二行起为请求头部,HOST 将指出请求的目的地;User-Agent,服务器端和客户端脚本都能访问它。它是浏览器类型检测逻辑的重要基础。该信息由你的浏览器来定义,并且在每个请求中自动发送等等 -
第三部分:空行,请求头部后面的空行是必须的
即使第四部分的请求数据为空,也必须有空行。 -
第四部分:请求数据也叫主体,可以添加任意的其他数据。一般为空
POST请求:
POST / HTTP1.1
Host:www.wrox.com
User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Content-Type:application/x-www-form-urlencoded
Content-Length:40
Connection: Keep-Alive
name=zzc&age=24
- 第一部分:请求行,第一行明了是post请求,以及http1.1版本。
- 第二部分:请求头部,第二行至第六行。
- 第三部分:空行,第七行的空行。
- 第四部分:请求数据:
name=zzc&age=24
GET 和 POST 提交方式的区别:
GET
提交,请求的数据会附在URL之后(把数据放置在请求行(request line)中),以 ? 分割 URL和传输数据,多个参数用 & 连接;url 的编码格式采用的是 ASCII 码,而不是 Unicode,这也就是说你不能在 url 中包含任何非 ASCII 字符,所有非 ASCII 字符均需要编码再传输。关于 url 编码可参考:URL编码与解码POST
提交:把提交的数据放置在是 HTTP 包的包体中。即:请求体 body 中。
响应消息 Response
一般情况下,服务器接收并处理客户端发过来的请求后会返回一个 HTTP 的响应消息。
HTTP响应也由四个部分组成,分别是:
- 状态行
- 消息报头
- 空行
- 响应正文
如下图:
HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8
<html>
<head></head>
<body>
<!--body goes here-->
</body>
</html>
-
第一部分:状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok) -
第二部分:消息报头,用来说明客户端要使用的一些附加信息
第二行和第三行为消息报头,
Date:生成响应的日期和时间;Content-Type:指定了MIME类型的HTML(text/html),编码类型是UTF-8 -
第三部分:空行,消息报头后面的空行是必须的
-
第四部分:响应正文,服务器返回给客户端的文本信息。
空行后面的html部分为响应正文。
注意:在响应消息中,也返回了 Content-Type
字段。它用来告诉客户端实际返回的内容的内容类型。
2. Content-Type
Content-Type
是 HTTP 的实体首部字段,在 request 的请求行或 response 的状态之后(请求头和响应头中都存在),也是首部的一部分,用于说明请求或返回的消息主体是用何种方式编码。
Content-Type 的常用类型:
Content-Type 的常用类型:
application/x-www-form-urlencoded
:表单 Form 提交(encType=application/x-www-form-urlencoded)multipart/form-data
:表单 Form 提交(encType=multipart/form-data)application/json
:JSON 提交
Ps:在 Form 元素的语法中,encType 表明提交数据的格式:指定将数据回发到服务器时浏览器使用的编码类型。
application/x-www-form-urlencoded
:窗体数据被编码为名称/值对。这是标准的编码格式
multipart/form-data
:窗体数据被编码为一条消息,页上的每个控件对应消息中的一个部分
text/plain
: 窗体数据以纯文本形式进行编码,其中不含任何控件或格式字符
1. application/x-www-form-urlencoded:
application/x-www-form-urlencoded
是浏览器表单 POST
提交的默认方式。
POST
提交:把 form 中的数据封装到 HTTP 的请求体中,然后,发送到服务器GET
提交:提交的数据按照key1=value1&key2=value2
的方式进行编码(标准的编码格式)。其中, key 和 value 都进行了 URL 转码。就是 URL 里面的 QueryString
在服务器端可直接使用 request.getParamater()
方法获取参数(request.getInputStream() 或 request.getReader() 可获取到请求内容,再解析出具体的参数)。
如果没有 type=file
的控件,用默认的 application/x-www-form-urlencoded
就可以了。 但是如果有 type=file
的话,就要用到multipart/form-data
了。浏览器会把整个表单以控件为单位分割,并为每个部分加上 Content-Disposition(form-data或者file)
,Content-Type(默认为text/plain),name(控件name)等信息,并加上分割符(boundary)
2. multipart/form-data:
multipart/form-data
:是常见的 POST 数据提交的方式;支持向服务器发送二进制数据;多用于 文件上传
使用表单上传文件时,必须让 form 的 enctype 等于这个值,表单数据都保存在 HTTP 的请求体 body 部分,各个表单项之间用 boundary 分开,即:
<form action="/" method="post" enctype="multipart/form-data">
<input type="text" name="description" value="some text">
<input type="file" name="myFile">
<button type="submit">Submit</button>
</form>
传输的数据为:
POST /foo HTTP/1.1
Content-Length: 68137
Content-Type: multipart/form-data; boundary=---------------------------974767299852498929531610575
---------------------------974767299852498929531610575
Content-Disposition: form-data; name="description"
Content-Type:
some text
---------------------------974767299852498929531610575
Content-Disposition: form-data; name="myFile"; filename="foo.txt"
Content-Type: text/plain
(content of the uploaded file foo.txt)
--974767299852498929531610575--
首先生成了一个 boundary(很长很复杂,为了避免与正文内容重复。服务器会根据这个边界解析数据,划分段,每一段就是一项数据)用于分割不同的字段 。然后 Content-Type 里指明了数据是以 multipart/form-data 来编码,本次请求的 boundary 是什么内容。
消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 --boundary 开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。
如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 --boundary-- 标示结束。
通过 request.getInputStream()
方法获取数据(request.getParamater() 方法是无法获取数据的),但它获取的是一个 InputStream,无法直接取到指定的表单项。但可直接利用开源组件获取表单项。
如: Apache 的 fileupload 组件。
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> list = upload.parseRequest(request);
// 遍历 list 来获取参数
使用 SpringBoot 上传文件:
public class User {
private String username;
private String password;
private String touXiang;
}
实体类中的属性 touXiang 对应着下面的 html 页面的 file 类型。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
<h1>Hello World</h1>
<form action="/user/login" method="post" enctype="multipart/form-data">
<p>账号:<input type="text" name="username" /></p>
<p>密码:<input type="password" name="password" /></p>
<p>头像:<input type="file" name="file" /></p>
<input type="submit" value="登陆" />
</form>
</body>
</html>
上述的 typen = “file” 的输入框的 name 的值(file) 不要和实体类中的属性名 touXiang 一致。
@RestController
@RequestMapping("/user")
public class UserController {
private static final String FILEPATH = "E:/sql/";
@RequestMapping("/login")
public User login(String username, String password, MultipartFile file) {
String touXiang = FILEPATH + file.getOriginalFilename();
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setTouXiang(touXiang);
try {
file.transferTo(new File(touXiang));
} catch (IOException e) {
e.printStackTrace();
}
return user;
}
}
入参类型为 MultipartFile 的参数名(file)要和前端的 name 的值一致。
3.application/json:
application/json
:它是告诉服务器请求体是序列化后的 JSON 字符串;
{
"name": "zzc",
"age": 24
}
Spring 对它上传的数据有很好的支持,可以直接通过 @RequestBody 进行接收;
application/x-www-form-urlencoded 与 application/json 的区别:
区别一:
application/x-www-form-urlencoded
:浏览器表单提交的默认方式;JQuery 的 AJAX 请求的默认方式。键值对形式:key1=value1&key2=value2
application/json
:JSON 字符串格式
区别二:
application/x-www-form-urlencoded
:对象接收、@RequestParam注解 接收
application/json(axios默认使用)
:只能以 @ResquestBody注解(只能接收单个参数) 接收对象
3. @RequestBody、@RequestParam注解
3.1 使用 @RequestBody、@RequestParam注解
@RequestBody、@RequestParam注解都作用于 Controller 层的接口,用来获取参数。
- @RequestParam:获取参数。请求方式:GET、POST
- @RequestBody:将 JSON 格式的字符串转换为 JAVA 对象。请求方式:POST。
@RequestBody 注解是用来接收请求体的参数,由于 GET 请求的参数是拼接在 url 后面(且是键值对形式,不是 json字符串),位于请求头中,并非请求体。故,无法接受 GET 请求的参数。而 POST 请求的参数正是位于请求体中。故,能接收 POST 请求的参数。
@RequestParam 注解
@RequestParam
的原理是 Spring 将 request.getParameter()
中 的 Key-Value 参数 Map 转化成了参数`@RequestParam 修饰的基本类型数据或对象。
@RequestParam
注解能接收 Content-Type=application/x-www-form-urlencoded
的 GET、POST请求方式;超链接(GET 请求)
表单的 GET/POST 请求:Content-Type=application/x-www-form-urlencoded
页面传参:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body>
<!--默认表单的Content-Type=application/x-www-form-urlencoded-->
<form action="/user/login" method="get">
<p>账号:<input type="text" name="username" /></p>
<p>密码:<input type="password" name="password" /></p>
<input type="submit" value="登录" />
</form>
</body>
</html>
POSTMAN 模仿表单传参:
POSTMAN 模仿超链接传参:
后台接收:
@RestController
@RequestMapping("/param")
public class ParamController {
// 1. 直接使用参数接收
@GetMapping("/login")
public String login(String name, String password) {
return name + password;
}
// 2. 使用对象接收
@GetMapping("/login2")
public UserVo login2(UserVo userVo) {
return userVo;
}
// 3. 使用 @RequestParam 注解接收。其中,@RequestParam(val) 中的 val 是与前端传来的参数名一致(name对应的值)。
// 如:将 name="password" 改为 name="pwd"
@GetMapping("/login3")
public String login3(@RequestParam("name") String name, @RequestParam("password") String pwd) {
return name + pwd;
}
}
@Data
public class UserVo {
private String name;
private String password;
private Integer age;
}
@RequestParam注解 接收 application/json 格式编码的参数会报错。
POSTMAN 集合 传参:
或者:
后台代码(GET/POST 方法都可以):
@GetMapping("/getList")
public List<Integer> getList(@RequestParam("ids") List<Integer> ids) {
return ids;
}
POSTMAN 中的:Content-Type=application/x-www-form-urlencoded
只能是 POST 方法
@PostMapping("/getList2")
public String getList2(HttpServletRequest request) {
System.out.println(JSON.toJSONString(request.getParameterMap()));
return request.getParameter("id");
}
@PostMapping("/getList3")
public String getList3(User user) {
return user.getId();
}
@PostMapping("/getList5")
public String getList5(@RequestParam Map<String, String> map) {
return map.get("id");
}
@PostMapping("/getList6")
public String getList6(@RequestParam String id) {
return id;
}
@PostMapping("/getList7")
public String getList7(String id) {
return id;
}
@RequestBody 注解
只接收 application/json 格式的参数。
使用 POSTMAN 进行 POST 请求
后台接收:
@PostMapping("/login4")
public UserVo login4(@RequestBody UserVo userVo) {
return userVo;
}
接口集合:
后台代码:
@PostMapping("/postListJson")
public List<Integer> postListJson(@RequestBody List<Integer> ids) {
return ids;
}
3.2 Controller 层的方法的多个入参无法使用多个 @RequestBody注解
@RequestBody注解是在当前的对象中只获取一次整个 HTTP 请求的 body 里面的所有数据,因此, Spring 就不可能将这个数据强制包装成 A 参数和 B参数,也就没必要在 Controller 层的方法的形参列表中出现多个 @RequestBody 注解。
public class Address {
private String province;
private String city;
// getter()/setter()
}
后台接收:
@PostMapping(value = "/login5")
public UserVo login5(@RequestBody UserVo userVo, @RequestBody Address address) {
return userVo;
}
请求错误。
解决方案:把 UserVo 和 Address 封装为一个大对象 Combine
public class Combine {
private UserVo userVo;
private Address address;
}
后台接收:
@PostMapping(value = "/login")
public Combine login(@RequestBody Combine combine) {
return combine;
}
3.3 @RequestBody 接收 JSON 字符串数组
public class User {
private String id;
private String username;
// getter()/setter()
}
public class Team {
private String teamName;
// 或者是数组: private User [] users;
private List<User> users;
// getter()/setter()
}
前端请求:
后端接收:
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/team")
public Team team(@RequestBody Team team) {
return team;
}
}