本文介绍: 作为后端,写完接口一定要自测,而Postman可以说是最常用接口测试工具了。但是在介绍Postman使用之前,我们需要复习一下GET/POST请求的一些细节

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

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

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

主要内容

环境准备

pom

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId&gt;spring-boot-starter-web</artifactId&gt;
    </dependency&gt;
    <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个信息

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个信息:

小结

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

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

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

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

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

3种POST请求

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

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

普通表单提交

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

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

我们在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&amp;age=xxx&amp;file=xxx的形式放入请求体。

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

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

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

formdata形式提交
<!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的作用是告诉服务器本次提交的参数编码类型,而它的值取决于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

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

是不是觉得很烦?

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

如果你用的是SpringMVC,那就更方便了,人家已经集成commonsfileupload依赖,在请求到达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&amp;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

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

原文地址:https://blog.csdn.net/smart_an/article/details/134760780

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_49030.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注