以下内容并不保证其准确性,但具有可参考性。
HTTP 协议 是一个应用层协议,它是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。
HTTP 协议 规定:POST 提交的数据必须包含在消息主体中的 Entity-body 中,但是协议并没有规定数据使用什么编码方式。开发者可以自己决定消息主体的格式。
数据发送出去后,需要(接收的)服务器解析,一般服务器会根据 Content-Type 字段来获取参数是如何编码的,然后,再对应去解码。
客户端发送一个HTTP请求到服务器的请求消息包括以下格式:
请求行请求头部空行请求数据如下图: GET 请求:
GET /562f25980001b1b106000338.jpg 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,[/562f25980001b1b106000338.jpg]为要访问的资源,该行的最后一部分说明使用的是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=Professional%20Ajax&publisher=Wiley 第一部分:请求行,第一行明了是post请求,以及http1.1版本。第二部分:请求头部,第二行至第六行。第三部分:空行,第七行的空行。第四部分:请求数据,第八行。一般情况下,服务器接收并处理客户端发过来的请求后会返回一个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 是 HTTP 的实体首部字段,在 request 的请求行或 response 的状态之后(在request header 和 response header 里都存在),也是首部的一部分,用于说明请求或返回的消息主体是用何种方式编码。
浏览器表单 POST 提交的默认方式;
POST 方式:把 form 中的数据封装到 HTTP Body 中,然后,发送到 server。 GET 方式:提交的数据按照 key1=value1&key2=value2 的方式进行编码(标准的编码格式)。其中, key 和 value 都进行了 URL 转码。就是 URL 里面的 QueryString可直接使用 request.getParamater() 方法获取参数(request.getInputStream() 或 request.getReader() 可获取到请求内容,再解析出具体的参数)。
常见的 POST 数据提交的方式;
支持向服务器发送二进制数据;
多用于 文件上传(使用表单上传文件时,必须让 form 的 enctype 等于这个值),表单数据都保存在 HTTP 的正文部分,各个表单项之间用 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 的值一致。
使用 SpringBoot 下载文件:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Index</title> </head> <body> <button onclick="downLoad()">导出</button> </body> <script type="application/javascript"> function downLoad() { window.location.href = '/user/download' } </script> </html> @RestController @RequestMapping("/user") public class UserController { private static final String FILEPATH = "E:/sql/"; @RequestMapping("/download") public void downloadFile(HttpServletResponse response) throws Exception{ String path = FILEPATH + "1.jpg"; File file = new File(path); if (!file.exists()) { throw new RuntimeException("文件不存在"); } // 配置文件下载 response.setContentType("application/force-download"); // 设置文件中文名乱码 response.addHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(file.getName(), "UTF-8")); // 实现文件下载 byte[] bytes = new byte[1024]; try(FileInputStream inputStream = new FileInputStream(file); BufferedInputStream in = new BufferedInputStream(inputStream)) { OutputStream out = response.getOutputStream(); int len = in.read(bytes); while (len != -1) { out.write(bytes, 0, len); len = in.read(bytes); } } } }消息主体是序列化后的 JSON 字符串;
{"title":"test","sub":[1,2,3]}由于 JSON 规范的流行,各大浏览器都开始原生支持 JSON.stringfy();
Spring 对它上传的数据有很好的支持,可以直接通过 @RequestBody 进行接收;
可以方便地提交复杂的结构化数据,特别适合 RESTful 的接口
它是一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范。
Content-Type: text/xml <!--?xml version="1.0"?--> <methodcall> <methodname>examples.getStateName</methodname> <params> <param> <value><i4>41</i4></value> </param> </params> </methodcall>数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符。
application/x-www-form-urlencoded:浏览器表单提交的默认方式、JQuery 的 AJAX 请求的默认方式。键值对形式:key1=value1&key2=value2
application/json:JSON 字符串格式
application/x-www-form-urlencoded:对象接收、@RequestParam注解 接收
application/json(axios默认使用):只能以 @ResquestBody注解(只能接收单个参数) 接收对象
@RequestBody、@RequestParam注解都作用于 Controller 层的接口,用来获取参数。
@RequestParam:获取参数。请求方式:GET、POST@RequestBody:将 JSON 格式的字符串转换为 JAVA对象。请求方式:POST。@RequestBody注解是用来接收请求体的参数,由于 GET 请求的参数是拼接在 url 后面(且是键值对形式,不是 json字符串),位于请求头中,并非请求体。故,无法接受 GET 请求的参数。而 POST 请求的参数正是位于请求体中。故,能接收 POST 请求的参数。@RequestParam注解 只能接收 Content-Type=application/x-www-form-urlencoded, GET、POST请求方式
public class User { private String username; private String password; // getter()/setter() }GET/POST 请求:
<!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>后台接收:
@RestController @RequestMapping("/user") public class UserController { // 1. 直接使用参数接收 @GetMapping("/login") public User login() { user.setUsername(username); user.setPassword(password); return user; } // 2. 使用对象接收 @GetMapping("/login") public User login(User user) { return user; } // 3. 使用 @RequestParam 注解接收。其中,@RequestParam(val) 中的 val 是与前端传来的参数名一致(name对应的值)。 // 如:将 name="password" 改为 name="pwd" @GetMapping("/login") public User login(@RequestParam("username") String username, @RequestParam("pwd") String password) { return user; } }@RequestParam注解 接收 application/json 格式编码的参数会报错。
只接收 application/json 格式编码的参数。
使用 POSTMAN 进行 POST 请求
@PostMapping(value = "/login") public User login(@RequestBody User user) { return user; } @RequestBody注解是在当前的对象中只获取一次整个 HTTP 请求的 body 里面的所有数据,因此, Spring 就不可能将这个数据强制包装成 A 参数和 B参数,也就没必要在 Controller 层的方法的形参列表中出现多个 @RequestBody 注解。
public class Address { private String province; private String city; // getter()/setter() }后台接收:
@PostMapping(value = "/login") public User login(@RequestBody User user, @RequestBody Address address) { return user; }请求错误。
解决方案:把 User 和 Address 封装为一个大对象 Combine
public class Combine { private User user; private Address address; }后台接收:
@PostMapping(value = "/login") public Combine login(@RequestBody Combine combine) { return combine; }前端请求:
后端接收:
@RestController @RequestMapping("/user") public class UserController { @PostMapping("/team") public Team team(@RequestBody Team team) { return team; } }