前后端联调httpservletrequest请求参数类型Content-type和tomcat源码简析

前言
前段时间和前端对接时碰到了参数接收不了的情况,即它post发送application/json格式的数据,后端用httpservletrequest接收,但是出现了httpservletrequest接收不到json格式数据,但是能接收application/x-www-form-urlencoded格式的表单数据。但是表单数据毕竟不是json格式,即使接收到了也不太好转类型。后来查阅资料才发现是自己取参数的方法不对,之前没深思过入参的封装,于是就想趁着这个机会彻底搞明白content-type、application/json 、application/x-www-form-urlencoded、httpservletrequest等的概念与功能上的联系。

本文核心则主要是回答以下几个问题:

1、content-type是什么,其构成格式是什么,HTTP传输中其作用是什么,有哪些值

content-type也是Internet Media Type,即互联网媒体类型,也叫做MIME类型(最初MIME是用于电子邮件系统的,后来HTTP也采用了这一方案)。在互联网中有多种不同的数据类型,HTTP在传输数据对象时会根据不同传参设定不同的数据格式标签,用于区分不同的数据类型。

content-type组成格式为 type/subtype;parameter  ,其中type指定主类型,可以是任意的字符串,如text,如果是*号代表所有。subtype指定子类型,可以是任意的字符串,如html,如果是*号代表所有,用“/”与主类型隔开,parameter则指定charset,boundary等可选参数。

在HTTP协议消息头中,使用Content-Type来表示请求和响应中的媒体类型信息。它用来告诉服务端如何处理请求的数据,以及告诉客户端(一般是浏览器)如何解析响应的数据,比如显示图片,解析并展示html等等。

常用的有application/json 、 application/x-www-form-urlencoded以及专门用于文件发送的multipart/form-data格式

2、application/json、 application/x-www-form-urlencoded、multipart/form-data 区别,payload、form data又是什么,附带参数时二者传输的区别是什么

application/json 、application/x-www-form-urlencoded、multipart/form-data、都是content-type里的某一种类型,前者用于设定参数为json字符串格式,中者用于设定参数被编码为key=value形式的键值对格式,后者则用于设定文件传输类型。

可能有的人对payload、form data不是很熟,这个是我在F12查看前端请求时发现的一个属性。经过查阅和测试,发现它们和参数的展示形式有关,根据其类型和值,大致可以判断参数在httpservletrequest中的存储位置与存储形式(其实我更想知道这payload和form data的出处、定义,查阅资料有的说是ajax发送数据方式,有的说是客户端组装方式的类型,这让我有点懵,如果有确切知道的小伙伴希望可以不吝留言告知)。payload也叫请求负载,一般指json格式数据,form data一般指键值对数据。
当我们post提交application/x-www-form-urlencoded表单类型数据或者multipart/form-data文件类型数据时,F12看到的基本就是from data格式数据。
当post提交application/json类型数据时,F12看到的基本就是payload格式数据。
不过也有例外,如果post提交表单(键值对)数据,然后conten-type设置为application/json,那么F12看到是payload,但是却是键值对数据。
另外,我发现payload和form data其实都和content-type以及post、get提交方式有关,而且我抓包发现这两个值在后端发往前端时是没有的。所以在java后端开发人员判断入参形式与位置时研究payload和form data其实有点舍本逐末。不如直接研究content-type,当然如果此时手头没资源,通过浏览器直接查看也是一种方式。

multipart/form-data一般用于文件传输,这个基本没什么争议,下面主要说下application/json 、application/x-www-form-urlencoded形式get、post传参的验证流程与结果:
httpservletrequest存储参数的地方一般有两块,一块是流,一块是map集合,代码中每轮都会打印出流和集合中的参数。代码中的获取方式可参考如下代码:

    @RequestMapping("/testRequestBody")
    @ResponseBody
    public String testRequestBody(HttpServletRequest request) {
        StringBuffer sb = new StringBuffer();
        System.out.println("从键值对集合获取参数:" + JSONObject.toJSONString(request.getParameterMap()));
        try (BufferedReader br = request.getReader()) {
            String str = null;
            while ((str = br.readLine()) != null) {
                sb.append(str);
            }
        } catch (Exception e) {
            System.out.println("异常" + e);
        }
        System.out.println("从流中获取请求体:" + sb.toString());
        return sb.toString();
    }

application/json 、application/x-www-form-urlencoded带参传输结果:
如果是get请求,不论是json还是form表单,参数都在请求体流中。
如果是post请求,只要是声明发送form表单数据,参数都在键值对parameterMap集合中。即使这时发送了一个json串,同样会放在键值对集合中,此时的key为json,值为空串(所以尽量避免传输与content-type不匹配的数据)。
如果是post请求,当声明发送json数据时,如果发送一个json体,则参数会放置在请求体流中。(发送键值对数据的场景没模拟出来,模拟出来后再补上。)
总而言之,前后端联调传参时,参数一般不是在流中就是在map集合中,如果不知道什么类型在流什么类型在集合,可以打断点看一下,然后编辑对应的代码接收。

3、httpservletrequest是什么,如何产生的,由谁管控

httpservletrequest是一个接口,其实现类Request由tomcat产生和维护。

4、Request生成和设置类中参数源码

大体流程:了解tomcat结构的可以知道,tomcat主要由connector喝container两部分组成。Connector接收请求,封装后传给container。这里接收是通过socket接收数据流,并生成原始的request对象。最后转成我们所熟知的servletrequest对象。

源码版本:8.5.63

如何搭建tomcat源码以及解决源码运行中出现的乱码问题可以参考这篇文章:https://blog.csdn.net/Interest1_wyt/article/details/117379563

前言:Request有两个,分别是org.apache.coyote.Request 和org.apache.catalina.connector.Request,一般是先有前者再有后者,后者的部分方法则是依赖前者来实现。所以可以认为前者是最开始出现的请求对象,里面封装有参数。而后者则是HttpServletRequest的实现类,一般是距离我们直接使用最近的类。
4.1 首先是NioEndpoint内部类Acceptor接收到socket请求

4.2 接收到socket请求后,接下来会再进入NioEndpoint的SocketProcessor内部类,在该类中开始处理socket。

4.3 不同的会话连接,有不同的处理协议,因为初步处理socket流程相同,所以这里初步处理socket协议是在AbstractProtocol抽象类的内部类ConnectionHandler中进行。该类中会再根据连接类型选择或创建对应的协议处理对象。

在创建协议处理对象时,默认会第一次开始创建org.apache.coyote.Request对象



4.4 随后经过选择,进入对应协议处理器的service方法中。本次请求为http请求,所以进入的是http处理类中。这个方法很重要,对于socket中相关信息的提取就是从该方法开始。该方法提取了键值对信息,并把请求头流信息放置在缓冲区,后面的请求体流获取就是从缓冲区中提取的信息。这一块说实话真没看太明白,只是经过各种测试,该方法之前和之后断点查询参数都是查不到,所以推测参数的生成是在这个方法中。如果对这一块有兴趣的小伙伴,可以参考这篇文章:(https://blog.csdn.net/wuqinduo/article/details/100630172

首先是初始化了缓冲区,用于从socket会话中提取请求头、url等有用信息

开始解析处理请求头信息

4.5 通过上一步的提取,socket中请求头、url中带有的键值对参数已经被存储到org.apache.coyote.Request中。接下来在Connector与Container的适配器类CoyoteAdapter中首次将org.apache.coyote.Request转换为org.apache.catalina.connector.Request 

4.6 随后进入Engine、Host、Context、Wrapper容器的调用流程中。



4.7 Wrapper中的invoke也是一个相对重要方法。里面定义了servlet、filter的生成与调用,同时也是在过滤器中 org.apache.catalina.connector.Request转变为





注意这里是在过滤器全部执行完之后再进入的service方法

4.8 再HttpServlet的service方法中请求对象彻底转换成我们所熟知的HttpServletRequest类型,后续就是根据路径进入指定的war中调用指定的执行方法了。

如上流程可简单梳理为:Socket -> SocketWrapper -> org.apache.coyote.Request -> org.apache.catalina.connector.Request -> javax.servlet.ServletRequest  -> HttpServletRequest

5、引申:什么是rest,什么是restful风格

REST全称是Representational State Transfer,它是2000年提出的一种软件架构风格,中文名称是表现层状态转移。REST之所以晦涩难懂,是因为前面主语(Resource )被去掉了。补全后为Resource Representational State Transfer,中文翻译可简单理解为资源在网络中以某种表现形式进行状态转移。简而言之就是URL中只使用名词来定位资源,用HTTP协议里的动词(GET、POST、PUT、DELETE)来标识资源的增删改查。

rest是一种组织web服务的架构(既不是一种技术,也不是一种标准),其定义了一些列的约束规范。如使用b/s,c/s模型、层次化、无状态、可缓存、统一接口等。如果一个系统满足rest定义的约束规范,那么这个系统就可以被称为restful风格的。

优点:前后端分离
缺点:无状态

6、期待指导
因为在参数具体生成那块源码没看太懂,所以没能细致的进行分析。如果有知道这块内容的小伙伴,希望可以指导指导,在这先谢谢哈。后续我也会抽时间再尝试看下这块内容,然后尽量把该文章完善起来。(因为工作太忙,该文章从构思到现在差不多一个多月才算完成,本来想再完善完善然后发的,但是怕拖着拖着给忘了,如果文章有不足的地方,欢迎你的指正哈)

参考链接:
https://www.jianshu.com/p/de5845b4c095 简书 content-type
https://www.cnblogs.com/tulintao/p/12178170.html payload和form data
https://blog.csdn.net/zhangcongyi420/article/details/111338879 tomcat源码解析
https://blog.csdn.net/SeniorShen/article/details/111591122 rest规范
https://blog.csdn.net/wuqinduo/article/details/100630172 tomcat中socket解析提取细节

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值