前后端数据传输格式(上)

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

作为后端,写完接口一定要自测,而Postman可以说是最常用的接口测试工具了。但是在介绍Postman的使用之前,我们需要复习一下GET/POST请求的一些细节。据我观察,其实无论前端还是后端,很多人对POST的了解都不够:

主要内容:

  • 环境准备
  • HTTP请求格式
  • 3种POST请求
    • 普通表单提交
    • 文件表单提交
    • JSON提交
  • Postman测试

环境准备

pom

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>

启动类(@ServletComponentScan扫描Servlet)

@ServletComponentScan
@SpringBootApplication
public class RequestDemoApplication {public static void main(String[] args) {SpringApplication.run(RequestDemoApplication.class, args);}}

Servlet

@WebServlet(urlPatterns = "/upload")
@Slf4j
public class UploadServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String nameParam = request.getParameter("name");String ageParam = request.getParameter("age");log.info("GET请求 name:{}, age:{}", nameParam, ageParam);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String nameParam = request.getParameter("name");String ageParam = request.getParameter("age");log.info("POST请求 name:{}, age:{}", nameParam, ageParam);}
}

创建表单页面form.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body></body>
</html>

HTTP请求格式

HTTP请求的格式可以笼统地分为

  • 请求行
  • 请求头
  • 空行
  • 请求体

启动程序,分别发送GET/POST请求并观察HTTP报文(当前案例中,浏览器使用firefox效果好一些)。

GET

http://localhost:8080/upload?name=周星驰&age=18

 结合上面几张图分析,可以得出3个信息:

  • GET请求没有请求体,但参数会附在URL后面传递
  • GET请求的参数会经过UrlEncoder编码(所以URL的中文是"%E5%91...")
  • Servlet可以通过request.getParameter()取到参数

POST

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h3>普通表单提交:默认enctype="application/x-www-form-urlencoded"</h3>
<form action="/upload" method="post">用户名:<input type="text" name="name"/><br>年龄:<input type="text" name="age"/><br><input type="submit" value="提交"/>
</form></body>
</html>

结合上面几张图分析,也可以得出3个信息:

  • POST请求有请求体,参数在请求体里
  • 请求体里的参数会经过UrlEncoder编码
  • Servlet通过request.getParameter()可以取到参数

小结

无论GET请求还是POST普通表单提交,后端都可以用request.getParameter()取到参数。

不是说GET参数是跟在URL里,而POST参数在请求体里吗,为什么都可以通过request.getParameter()得到?

其实不论是GET还是POST普通表单请求参数都是编码后以name=xxx&age=xxx&file=xxx的形式存在的,只不过GET的参数跟在URL中,而POST放在请求体里。服务器接收到请求后,会根据请求类型是GET还是POST,分别从对应位置取出name=xxx&age=xxx&file=xxx并进行解析、分割,然后存在request的一个map中。

这里之所以强调POST表单请求,是因为POST其实有很多种形式,仅仅是POST表单请求和GET比较相似,其他几种POST形式和GET区别较大,后面会介绍。

对了,有一个很有意思的现象,GET和POST普通表单真的太像了,对于Spring的Controller来说,GET请求的参数可以是散装的,也可以用POJO接收,POST表单请求也是如此(动手试一下)!

3种POST请求

复习完HTTP请求的格式后,我们着重聊聊POST请求(GET太简单了,略过)。

POST请求中,开发常见的有3种方式:

  • 普通表单提交
  • 文件表单提交
  • JSON提交

普通表单提交

上面已经试过了,它的特点是:

  • 默认的编码方式是enctype="application/x-www-form-urlencoded"
  • 会对每一个表单项进行编码("周星驰" --> "%E5%91%A8%...")
  • 不能提交文件流
  • 后端可以通过request.getParameter()得到每个参数

有两点需要证明一下:默认编码是什么、能否提交文件流。

我们在HTML的表单中并没有指定enctype,然后HTTP报文中却显示:

所以POST表单请求默认编码方式是enctype="application/x-www-form-urlencoded"。我们从name=“周星驰”这个参数的乱码也可以看出,肯定经过了某种编码。

如果你观察过Postman的POST,其实就有这一个选项:

那么enctype="application/x-www-form-urlencoded"这种编码格式能不能上传文件流呢?比如,一个用户表单,能不能同时上传头像呢?

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h3>普通表单提交:默认enctype="application/x-www-form-urlencoded"</h3>
<form action="/upload" method="post">用户名:<input type="text" name="name"/><br>年龄:<input type="text" name="age"/><br>选择文件:<input type="file" name="file"/><br><input type="submit" value="提交"/>
</form></body>
</html>
@WebServlet(urlPatterns = "/upload")
@Slf4j
public class UploadServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String nameParam = request.getParameter("name");String ageParam = request.getParameter("age");log.info("GET请求 name:{}, age:{}", nameParam, ageParam);}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String nameParam = request.getParameter("name");String ageParam = request.getParameter("age");// 根据参数名获取文件String fileParam = request.getParameter("file");log.info("POST请求 name:{}, age:{}, file:{}", nameParam, ageParam, fileParam);}
}

通过实验我们发现,POST普通表单是无法上传文件的,最多只能传递文件名(请求体中是file=xxxx.png)。

文件表单提交

所谓文件表单提交,指的是此时表单中含有文件项。

在上面普通表单提交的实验中,我们已经发现,光是设置input输入框为type="file"是不够的,这样只是提交文件名,而不是文件流。

归根到底,造成这种现象的原因在于当前表单的编码格式不对。POST请求默认的编码格式是:enctype="application/x-www-form-urlencoded",也就是对各个表单项的参数值进行URL编码后以name=xxx&age=xxx&file=xxx的形式放入请求体。

最终file仅仅提交了文件名,而不是文件流。

那么怎样才能把整个文件提交上去呢?

只要把POST请求的编码改为enctype="multipart/form-data"即可。

form-data形式提交
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h3>文件表单提交:必须指定enctype="multipart/form-data"</h3>
<form action="/upload" method="post" enctype="multipart/form-data">用户名:<input type="text" name="name"/><br>年龄:<input type="text" name="age"/><br>选择文件:<input type="file" name="file"/><br><input type="submit" value="提交"/>
</form></body>
</html>

看起来好像稳了,请求体有点不一样了!

但是回到SpringBoot一看,坏了:

之前普通表单提交虽然无法上传文件流,但好歹传了文件名呢,而这次干脆啥都没提交上去。

但是,真的什么都没提交上去吗?

其实前端已经提交成功,只是后端没按正确方式接收。

失败原因分析

我们观察一下设置enctype="multipart/form-data"后,请求体的变化:

application/x-www-form-urlencoded编码的请求体参数

multipart/form-data编码的请求体参数

浏览器会在发起POST请求时,自动带上Content-Type请求头:

  • 普通表单的Content-Type是:application/x-www-form-urlencoded(图一)
  • 文件表单的Content-Type是:multipart/form-data(图二)

Content-Type的作用是告诉服务器本次提交的参数编码类型,而它的值取决于enctype。

为什么multipart/form-data能实现文件上传?这要从multipart/form-data独特的格式说起。

multipart/form-data的意思是多部件传输请求,即把表单的每一项都作为一个部件,单独发送,每一个部件其实都包含:

  • 请求头
  • 空行 
  • 请求体

-----------------------------5708493371455355810326359340
Content-Disposition: form-data; name="name"å¨æé©°
-----------------------------5708493371455355810326359340
Content-Disposition: form-data; name="age"18
-----------------------------5708493371455355810326359340
Content-Disposition: form-data; name="file"; filename="avatar.png"
Content-Type: image/pngPNG

简单来说,就是这样:

其中,file项显示乱码恰恰代表本次上传的是文件(你本地直接用文本编辑器打开一张照片也是如此)。

所以,此时所有POST参数(包括整个文件)都已经发送到后端了。

在application/x-www-form-urlencoded格式下,服务器会帮我们分割参数并存入Map中,所以我们才能通过request.getParameter()获取参数。但由于这次ContentType是multipart/form-data,服务器将不再对参数进行解析,而是直接放入了request的inputStream中,如果想要获取参数,开发人员必须自己处理。

SpringMVC接收File

要想得到请求参数,大致的处理流程是:

  • 识别分隔符"-----------5708493371455355810326359340"
  • 根据分隔符把参数切成三份
  • 从请求头中得到参数名
  • 从请求体中得到参数值。如果Content-Type:image/png,还要把字符转为图片

是不是觉得很烦?

所幸,已经有人替我们做了。如果你用的是Servlet,那么可以引入Apache的commons-fileupload依赖 ,它的原理就是上面说的那样。

如果你用的是SpringMVC,那就更方便了,人家已经集成了commons-fileupload依赖,在请求到达Controller方法之前,已经替我们把文件封装到MultipartFile组件中:

@Slf4j
@RestController
public class UploadController {@PostMapping("/springUpload")public void upload(String name, Integer age, MultipartFile file) {log.info("name:{}, age:{}, fileName:{}", name, age, file.getOriginalFilename());}
}

此时参数file是整个文件,我们可以通过file.getInputStream()获取文件流。

整个过程大致是这样的:

POST表单请求 --> name=xxx&age=xxx,服务器能处理,存入Map --> request.getParameter()

POST文件上传 --> 服务器无法处理,全部塞入Request的InputStream --> 第三方组件分割多个参数,File参数会被单独包装成MultipartFile(包含文件流、文件名等)

上面是通过HTM的form表单实现的(enctype指定form-data),我们也可以使用Postman发送表单请求:

JSON提交

引入JQuery啥的比较麻烦,我们直接用Postman模拟。总之,HTML+Ajax能做的,都可以通过Postman模拟。

Postman测试

GET请求

POST请求:none

none就是没有请求体,但编码还是默认的enctype="application/x-www-form-urlencoded"

POST请求:x-www-form-urlencoded

有请求体,编码是默认的enctype="application/x-www-form-urlencoded"

POST请求:multipart/form-data

POST请求:raw+Json

虽然上面说了那么多种POST请求形式,但实际上前后端分离后已经很少使用其他几种POST形式了,基本都是JSON。

它们的使用频率大概是:

none:几乎为0%

x-www-form-urlencoded:10%

multipart/form-data:20%(比如OSS文件上传,有可能会配合MultipartFile单独写一个)

raw+json:70%(绝大多数POST都是JSON,后端需要用@RequestBody接收)

所以,这里我们不再演示文件提交,而是演示JSON请求(以POST新增User为例)

POJO

@Data
public class UploadUser {/*** 姓名*/private String name;/*** 年龄*/private Integer age;/*** 地址*/private String address;}

Controller

@RestController
@Slf4j
public class UserController {/*** 新增用户* @param user* @return*/@PostMapping("addUser")public UploadUser addUser(UploadUser user) {log.info("user:{}", user);return user;}}

Postman发起POST请求(raw+JSON)

后端接口压根没取到参数:

因为现在发起的请求是JSON,所以后面必须明确以JSON格式解析:

请求成功:

什么时候加@RequestBody?

  • POST请求是JSON时,必须加@RequestBody
  • POST请求时普通表单时,不用加,加了反而错
  • POST请求是文件上传时,不用加,用MultipartFile

规则这么多,说了等于没说。关键还是要明白@RequestBody到底意味着什么?

@RequestBody的意思是:SpringMVC将会用jackson解析请求体中参数,其他非JSON格式的参数会直接抛异常

SpringBoot默认使用jackson解析JSON

打开Postman的请求控制台:

分别发送x-www-form-urlencoded、form-data、raw+JSON三种请求:

当前端指定Content-Type为json,也就是发送JSON请求时,后端就要加@RequestBody,其他GET/POST普通表单/POST文件表单都不需要,加了反而错。

示意图

三种POST形式,参数其实都是在请求体中,但格式有点不同。

如果把GET请求的格式也加上,你会发现:

可怜的服务器其实只会处理x-www-form-urlencoded这种简单的格式,做一些参数切割工作,然后封装到Map中,方便Servlet通过request.getParameter()获取。

其他的无论multipart/form-data还是application/json,都需要第三方框架在后面补充完成。

最后,我相信部分同学可能对@RequestBody/@ResponseBody如何起作用比较感兴趣,如果后面有多余篇幅的话,可以一起探究一下。其实不是特别重要。

提问

1.GET和POST的请求参数分别在哪?默认是什么编码?

2.文章把POST请求分为哪几种?请求参数存放的位置一样吗?

3.普通表单请求和文件表单请求的区别是?

4.文件表单提交后,Servlet的request.getPatameter()方法就废了。如果不借助file-upload,你怎么获取参数?

5.后端接口方法什么时候加@RequestBody?

6.怎么用Postman分别发送文件表单、普通表单、JSON请求?

请在评论区尝试作答!

 

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/213549.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode—198.打家劫舍【中等】

2023每日刷题&#xff08;五十二&#xff09; Leetcode—198.打家劫舍 算法思想 具体思路 首先&#xff0c;我们从上面的题目描述中抽象出题意。 ● 从一个非负整数数组中找到一个子序列&#xff0c;并且该子序列的和最大 ● 子序列中每个数的位置不能够相邻。举例来讲&…

【C语言】动态内存管理(C语言的难点与精华,数据结构的前置知识,你真的掌握了吗?)

文章目录 引言一、为什么要动态内存分配二、动态内存分配的相关函数2.1 malloc2.2 free2.3 calloc2.4 realloc 三、常见的动态内存的错误3.1 对NULL指针的解引用3.2 对动态内存越界访问3.3 对非动态内存释放3.4 对动态内存部分释放3.5 对动态内存多次释放3.6 未对动态内存释放&…

Java-JDBC操作MySQL

Java-JDBC操作MySQL 文章目录 Java-JDBC操作MySQL一、Java-JDBC-MySQL的关系二、创建连接三、登录MySQL四、操作数据库1、返回型操作2、无返回型操作 练习题目及完整代码 一、Java-JDBC-MySQL的关系 #mermaid-svg-B7qjXrosQaCOwRos {font-family:"trebuchet ms",verd…

邮政快递查询,邮政快递单号查询,按物流更新量筛选出需要的单号

批量查询邮政快递单号的物流信息&#xff0c;按物流更新量将需要的单号筛选出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 邮政快递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;并登录 步骤2&#xff1a;点击主界…

PHP短信接口防刷防轰炸多重解决方案三(可正式使用)

短信接口盗刷轰炸&#xff1a;指的是黑客利用非法手段获取短信接口的访问权限&#xff0c;然后使用该接口发送大量垃圾短信给目标用户 短信验证码轰炸解决方案一(验证码类解决)-CSDN博客 短信验证码轰炸解决方案二(防止海外ip、限制ip、限制手机号次数解决)-CSDN博客 PHP短信…

《opencv实用探索·八》图像模糊之均值滤波、高斯滤波的简单理解

1、前言 什么是噪声&#xff1f; 该像素与周围像素的差别非常大&#xff0c;导致从视觉上就能看出该像素无法与周围像素组成可识别的图像信息&#xff0c;降低了整个图像的质量。这种“格格不入”的像素就被称为图像的噪声。如果图像中的噪声都是随机的纯黑像素或者纯白像素&am…

Fiddler抓包模拟器(雷电模拟器)

Fiddler设置 List item 打开fiddler,的options 点击OK,重启fiddler 模拟器 更改网络设置 IP可以在电脑上终端上查看 然后在模拟器浏览器中输入IP:端口 安装证书

[二分查找双指针]LeetCode881: 救生艇

救生艇 作者推荐 [二分查找]LeetCode2040:两个有序数组的第 K 小乘积 本文涉及的基础知识点 二分查找算法合集 题目 给定数组 people 。people[i]表示第 i 个人的体重 &#xff0c;船的数量不限&#xff0c;每艘船可以承载的最大重量为 limit。 每艘船最多可同时载两人&am…

[足式机器人]Part2 Dr. CAN学习笔记-数学基础Ch0-5Laplace Transform of Convolution卷积的拉普拉斯变换

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-数学基础Ch0-5Laplace Transform of Convolution卷积的拉普拉斯变换 Laplace Transform : X ( s ) L [ x ( t ) ] ∫ 0 ∞ x ( t ) e − s t d t X\left( s \right) \mathcal{L} \left[ x\lef…

QT作业2

使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是否为…

【Flink系列三】数据流图和任务链计算方式

上文介绍了如何计算并行度和slot的数量&#xff0c;本文介绍Flink代码提交后&#xff0c;如何生成计算的DAG数据流图。 程序和数据流图 所有的Flink程序都是由三部分组成的&#xff1a;Source、Transformation和Sink。Source负责读取数据源&#xff0c;Transformation利用各种…

Linux各目录结构说明

文章目录 目录说明源码放哪里&#xff1f;拓展&#xff1a;Linux里面安装软件是装在home目录还是opt目录还是/usr/local好&#xff1f; bin boot dev etc home lib lib64 lostfound media mnt opt proc root run sbin srv sys tmp usr var 目录说明 bin 存放二进制可执行文件&…

《Spring Cloud Alibaba 从入门到实战》分布式配置

分布式配置 1、简介 Nacos 提供用于存储配置和其他元数据的 key/value 存储&#xff0c;为分布式系统中的外部化配置提供服务器端和客户端支持。 Spring Cloud Alibaba Nacos Config 是 Config Server 和 Client 的替代方案&#xff0c;在特殊的 bootstrap 阶段&#xff0c;…

2023.12.4 关于 Spring Boot 统一异常处理

目录 引言 统一异常处理 异常全部监测 引言 将异常处理逻辑集中到一个地方&#xff0c;可以避免在每个控制器或业务逻辑中都编写相似的异常处理代码&#xff0c;这降低了代码的冗余&#xff0c;提高了代码的可维护性统一的异常处理使得调试和维护变得更加容易&#xff0c;通…

机器学习之无监督学习:九大聚类算法

今天&#xff0c;和大家分享一下机器学习之无监督学习中的常见的聚类方法。 今天&#xff0c;和大家分享一下机器学习之无监督学习中的常见的聚类方法。 在无监督学习中&#xff0c;我们的数据并不带有任何标签&#xff0c;因此在无监督学习中要做的就是将这一系列无标签的数…

Python实现PDF-Excel

轻松解决PDF格式转Excel&#xff08;使用python实现&#xff09; 实现思路&#xff1a; 要将PDF转换为Excel&#xff0c;可以使用以下步骤&#xff1a; 解析PDF内容&#xff1a;首先&#xff0c;需要使用Python中的第三方库&#xff08;如PyPDF2、pdfminer等&#xff09;来解…

Ribbon 饥饿加载

Ribbon默认是采用懒加载&#xff0c;即第一次访问时才会去创建LoadBalanceClient&#xff0c;请求时间会很长而饥饿加载则会在项目启动时创建&#xff0c;降低第一次访问的耗时&#xff0c;通过下面配置开启饥饿加载: 一、懒加载 Ribbon 默认为懒加载即在首次启动Application…

数据结构之插入排序

目录 前言 插入排序 直接插入排序 插入排序的时间复杂度 希尔排序 前言 在日常生活中&#xff0c;我们不经意间会遇到很多排序的场景&#xff0c;比如在某宝&#xff0c;某东上买东西&#xff0c;我们可以自己自定义价格是由高到低还是由低到高&#xff0c;再比如在王者某…

修改移远提供的GobiNet、quectel-CM源码,使其支持有方N720 4G模块

最近在研究imx6ull linux下4G模块驱动的移植&#xff0c;参考的移远ec20的移植方法&#xff0c;添加了GobiNet驱动&#xff0c;编译了quectel-CM工具&#xff0c;并且可以正常拨号&#xff0c;分配到ip&#xff0c;如下&#xff1a; ping外网也没有压力&#xff0c;如下…

Qt12.8

使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是否为…