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

news2025/4/13 12:37:35

作者简介:大家好,我是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 {

    @Override
    protected 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);
    }

    @Override
    protected 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 {

    @Override
    protected 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);
    }

    @Override
    protected 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/png

PNG

简单来说,就是这样:

其中,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.coloradmin.cn/o/1279336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

矩阵代数与MATLAB实现(特征值、广义特征值、酋矩阵、奇异值、托普利兹矩阵、汉克尔矩阵、范德蒙矩阵、)

矩阵代数的相关知识 目录 一、特征值与特征向量 1、特征值与特征向量 2、MATLAB计算 二、广义特征值与广义特征向量 1、广义特征值与广义特征向量 2、MATLAB计算 三、酋矩阵 1、酋矩阵 2、MATLAB计算 四、矩阵的奇异值分解 1、奇异值 2、MATLAB计算 五、托普利兹矩…

Spring事务管理介绍

文章目录 Spring事务管理1 Spring事务简介【重点】问题导入1.1 Spring事务作用1.2 需求和分析1.3 代码实现【前置工作】环境准备【第一步】在业务层接口上添加Spring事务管理【第二步】设置事务管理器(将事务管理器添加到IOC容器中)【第三步】开启注解式事务驱动【第四步】运行…

阿里系列-淘宝接口抓取及相关问题

阿里系-淘宝接口抓取 一、安装charlse抓包工具 官方下载地址 安装证书 二、安装xposed hook框架 Xponsed简介 具体安装步骤 三、安装模块 关闭阿里系ssl验证 开启http模式 支持支付宝、淘宝、淘宝直播各个接口抓取 四、效果如下 接下去一段时间更新阿里系相关接口 文章目录 一、…

WIN10 WIN11 关闭更新的绝佳办法(极简单无副作用)

WIN10 WIN11 关闭更新的绝佳办法&#xff08;极简单无副作用&#xff09; 极其简单用实用可以关闭更新20年 winr&#xff0c;输入regedit 打开注册表打开注册表的这个路径&#xff1a; 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 右键空白的地方…

Redis 入门、基础。(五种基本类型使用场景)

文章目录 1. 概况1.1 认识 NoSQL1.1.1 查询方式1.1.2 事务1.1.3 总结 2. 认识 Redis4. Redis 常见命令4.1 Redis 数据结构介绍4.2 Redis 通用命令4.3 Redis 命令之 String 命令4.4 Redis 命令的层级结构4.5 Redis 命令之 Hash 命令4.6 Redis 命令之 List 命令4.7 set 唯一不排序…

蓝桥杯第1037题子串分值和 C++ 字符串 逆向思维 巧解

题目 思路和解题方法 方案一——遍历哈希表 仅能过60%样例,大多数同学都用的该方法&#xff0c;就不过多赘述 #include <iostream> #include <unordered_map> using namespace std; int main() {string s;cin >> s;int n s.size();int res n;for (int i 0…

字符指针变量数组指针变量

字符指针变量 在指针的类型中&#xff0c;我们知道有一种指针叫做字符指针 一般的使用情况如下&#xff1a; #include<stdio.h> int main() {char ch w;char* pa &ch;*pa h;printf("%c", *pa);return 0; } 还有一种使用方法如下&#xff1a; #incl…

CRM在设备制造行业的应用,优化资源配置

设备制造业竞争激烈&#xff0c;公司要以客户为中心&#xff0c;搞好售后服务。CRM管理软件是设备制造业客户关系管理的重要工具。以下是CRM在设备制造业里的典型应用。 1.营销管理 制订市场策略&#xff1a;设备制造通常涉及较长的决策周期和销售周期。客户可能会在多家供货商…

听GPT 讲Rust源代码--src/tools(6)

File: rust/src/tools/rust-analyzer/crates/ide/src/references.rs 在Rust源代码中&#xff0c;references.rs文件位于rust-analyzer工具的ide模块中&#xff0c;其作用是实现了用于搜索引用的功能。 该文件包含了多个重要的结构体、特质和枚举类型&#xff0c;我将逐一介绍它…

英语助教求职简历模板(通用10篇)

以下10篇简历内容以英语助教招聘需求为背景制作&#xff0c;大家可以借鉴参考&#xff0c;希望能帮助大家在众多候选人中脱颖而出。 英语助教求职简历下载&#xff08;可在线制作)&#xff1a;百度幻主简历 英语助教简历1&#xff1a; 求职意向 求职类型&#xff1a;全职 …

基于Amazon Bedrock的企业级生成式AI平台

基于Amazon Bedrock的企业级生成式AI平台 2023.12.2版权声明&#xff1a;本文为博主chszs的原创文章&#xff0c;未经博主允许不得转载。 Amazon Bedrock 是一项新的 AWS 服务&#xff0c;可让企业通过 API 轻松利用和自定义生成式 AI 模型。公司现在可以构建和扩展人工智能应…

springboot教师进修培训管理系统设计与实现java+jsp

2.1研究目标 目前全国各地都陆续开展了中小学教师进修培训工作&#xff0c;以不断丰富中小学教师的专业知识&#xff0c;提高教学水平。针对传统进修培训管理模式效率低下&#xff0c;数据资料存储分散、混乱、易丢失的问题&#xff0c;以Java Web技术为基础&#xff0c;设计并…

Linux常用命令——atrm命令

在线Linux命令查询工具 atrm 删除待执行任务队列中的指定任务 补充说明 atrm命令用于删除待执行任务队列中的指定任务。 语法 atrm(选项)(参数)选项 -V&#xff1a;显示版本号。参数 任务号&#xff1a;指定待执行队列中要删除的任务。 实例 删除已经排队的任务 atq…

听GPT 讲Rust源代码--src/tools(7)

File: rust/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs 在Rust源代码中&#xff0c;rust-analyzer/crates/ide/src/inlay_hints/chaining.rs这个文件的作用是生成Rust代码中的链式调用提示。 具体来说&#xff0c;当我们使用链式调用时&#xff0c;例如A…

java+springboot校园一卡通学生卡管理系统+jsp

利用校园卡实现了学生在学校的身份认证&#xff0c;对学生在学校的各种消费提供了方便的途径。对于学校图书馆&#xff0c;将自动存储学生图书借阅情况&#xff0c;记录处罚情况.对于任课教师可以及时、准确、方便的了解学生出勤、作业等情况。是凭借发达的网络技术&#xff0c…

多线程06 单例模式,阻塞队列以及模拟实现

前言 上篇文章我们讲了wait和notify两个方法的使用.至此,多线程的一些基本操作就已经结束了,今天我们来谈谈多线程的一些简单应用场景. 单例模式 单例模式,顾名思义,只有一个实例的模式,我们有两种实现方式,分别是懒汉式和饿汉式,我们来分别给出代码. 饿汉式(此处的饿表示创建实…

【Spring】AOP实现公共字段填充

目录 1. 公共字段自动填充 1.1 问题分析 1.2 实现思路 1.3 代码开发 1.3.1 步骤一 1.3.2 步骤二 1.3.3 步骤三 1.4 功能测试 1.5 代码提交 1. 公共字段自动填充 1.1 问题分析 在上一章节我们已经完成了后台系统的员工管理功能和菜品分类功能的开发&#xff0c;在新…

L1-015:跟奥巴马一起画方块

题目描述 美国总统奥巴马不仅呼吁所有人都学习编程&#xff0c;甚至以身作则编写代码&#xff0c;成为美国历史上首位编写计算机代码的总统。2014年底&#xff0c;为庆祝“计算机科学教育周”正式启动&#xff0c;奥巴马编写了很简单的计算机代码&#xff1a;在屏幕上画一个正方…

实时流式计算 kafkaStream

文章目录 实时流式计算Kafka StreamKafka Streams 的关键概念KStreamKafka Stream入门案例编写SpringBoot 集成 Kafka Stream 实时流式计算 一般流式计算会与批量计算相比较 流式计算就相当于上图的右侧扶梯&#xff0c;是可以源源不断的产生数据&#xff0c;源源不断的接收数…

西南科技大学模拟电子技术实验四(集成运算放大器的线性应用)预习报告

一、计算/设计过程 说明:本实验是验证性实验,计算预测验证结果。是设计性实验一定要从系统指标计算出元件参数过程,越详细越好。用公式输入法完成相关公式内容,不得贴手写图片。(注意:从抽象公式直接得出结果,不得分,页数可根据内容调整) 反相比例运算电路(1)实验…