- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章springboot接口如何多次获取request中的body内容由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
在使用springboot开发接口时,会将参数转化为Bean,用来进行参数的自动校验。同时也想获取request中原始body报文进行验签(防止报文传输过程中被篡改).
因为通过将bean再转化为字符串后,body里面的报文格式、字段顺序会发生改变,就会导致验签失败。因此只能通过request来获取body里面的内容.
既想接口自动实现参数校验,同时又想获取request中的原始报文,因此我们可以通过在controller中的restful方法中,写入两个参数,获取多次request中的body内容.
那么如何实现多次读取body内容了(因为request里的body是以字节流的方式读取的,默认情况下读取一次,字节流就没有了),下面就来大致分析一下.
方法1、 。
1
|
public
R list(
@RequestBody
String rawMsg)
|
采用上述方式可以直接获得请求报文中的原始body信息,而且当body是一个json字符串时,rawMsg参数接口到的body值,不会改变json中key的顺序,即与发送方的body内容是保持一致的。这种方式可以用来对报文验签,因为被加密的字符串与发送方是保持一致的.
这种方式可以接受request里面body内容的原始格式,保持与发送方一致.
如下就可以对原始报文进行验签操作了 。
1
2
3
|
// 用公钥,对原始报文进行验签,在这里如果rawMsg里面是json时,当key的顺序改变后,会验签失败,
//如此我们可以通过request来获取body里面的原始报文
boolean
verifyResult = SignVerifyUtils.verifySignature(rawMsg, Constant.NPIS_PUBLIC_KEY);
|
方法2、 。
1
|
public
R list(
@RequestBody
@Validated
ReqBean<ABCReqBean> abcReqBean)
|
这种接受参数的方法,可以将request里的json报文,直接转换成对应的bean对象。并且可以用来校验参数,例如某个字段是必传的、某个字段的值最大是多少等等。例如 。
1
2
3
|
@NotNull
(message =
"日期字段不允许为空"
)
@Size
(min =
8
, max =
8
, message =
"日期字符串的长度必须为 8"
)
private
String beginDate;
|
有没有一种方法,既能同时利用参数校验功能,又能获取原始body里的内容来进行验签呢,这时候就可以采用下面的第3中方法.
1
2
3
|
@RequestMapping
(method = {RequestMethod.POST}, value =
"/dataQry"
)
public
R list(
@RequestBody
@Validated
ReqBean<ABCReqBean> abcReqBean,HttpServletRequest request){
}
|
在这里就可以通过将报文转换成abcReqBean对象,并实现接口参数的自动校验功能;同时可以利用request获取原始报文来进行验签.
注意:由于在接收参数时,HttpServletRequest只能读取一次body内容(因为是读的字节流,读完就没了),因此我们需要需要做特殊处理, 。
下面来看一种基于SpringBoot来解决HttpServletRequest只能读取一次的问题.
2.3.1 继承HttpServletRequestWrapper包装类,每次读取body后,再将参数写会request 。
为解决上述多次读取request中的body内容的问题,我们只需要将以下两个类,放到项目中即可,并通过@Component来注测为spring bean即可 。
继承HttpServletRequestWrapper ,实现每次读取request中的body后,在将内容写回request.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
package
com.abcd.config;
import
org.apache.commons.io.IOUtils;
import
javax.servlet.ReadListener;
import
javax.servlet.ServletInputStream;
import
javax.servlet.http.HttpServletRequest;
import
javax.servlet.http.HttpServletRequestWrapper;
import
java.io.*;
/**
* @author:
*/
public
class
RequestWrapper
extends
HttpServletRequestWrapper {
//参数字节数组
private
byte
[] requestBody;
//Http请求对象
private
HttpServletRequest request;
public
RequestWrapper(HttpServletRequest request)
throws
IOException {
super
(request);
this
.request = request;
}
/**
* @return
* @throws IOException
*/
@Override
public
ServletInputStream getInputStream()
throws
IOException {
/**
* 每次调用此方法时将数据流中的数据读取出来,然后再回填到InputStream之中
* 解决通过@RequestBody和@RequestParam(POST方式)读取一次后控制器拿不到参数问题
*/
if
(
null
==
this
.requestBody) {
ByteArrayOutputStream baos =
new
ByteArrayOutputStream();
IOUtils.copy(request.getInputStream(), baos);
this
.requestBody = baos.toByteArray();
}
final
ByteArrayInputStream bais =
new
ByteArrayInputStream(requestBody);
return
new
ServletInputStream() {
@Override
public
boolean
isFinished() {
return
false
;
}
@Override
public
boolean
isReady() {
return
false
;
}
@Override
public
void
setReadListener(ReadListener listener) {
}
@Override
public
int
read() {
return
bais.read();
}
};
}
public
byte
[] getRequestBody() {
return
requestBody;
}
@Override
public
BufferedReader getReader()
throws
IOException {
return
new
BufferedReader(
new
InputStreamReader(
this
.getInputStream()));
}
}
|
2.3.2 将包装类加入过滤器链 。
回写参数的包装类写好之后接下来就是加入过滤器链之中,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
package
com.abcd.config;
import
org.springframework.stereotype.Component;
import
javax.servlet.*;
import
javax.servlet.annotation.WebFilter;
import
javax.servlet.http.HttpServletRequest;
import
java.io.IOException;
/**
* @author:
*/
@Component
@WebFilter
(filterName =
"channelFilter"
, urlPatterns = {
"/*"
})
public
class
ChannelFilter
implements
Filter {
@Override
public
void
init(FilterConfig filterConfig)
throws
ServletException {
}
@Override
public
void
doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
try
{
ServletRequest requestWrapper =
null
;
if
(request
instanceof
HttpServletRequest) {
requestWrapper =
new
RequestWrapper((HttpServletRequest) request);
}
if
(requestWrapper ==
null
) {
chain.doFilter(request, response);
}
else
{
chain.doFilter(requestWrapper, response);
}
}
catch
(IOException e) {
e.printStackTrace();
}
catch
(ServletException e) {
e.printStackTrace();
}
}
@Override
public
void
destroy() {
}
}
|
项目有两个场景会用到从Request的Body中读取内容.
1、打印请求日志 。
2、提供Api接口,在api方法执行前,从Request Body中读取参数进行验签,验签通过后在执行api方法 。
2.1 自定义RequestWrapper 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
class
MyRequestWrapper
extends
HttpServletRequestWrapper {
private
final
String body;
public
MyRequestWrapper(HttpServletRequest request)
throws
IOException {
super
(request);
this
.body = RequestReadUtils.read(request);
}
public
String getBody() {
return
body;
}
@Override
public
ServletInputStream getInputStream()
throws
IOException {
final
ByteArrayInputStream bais =
new
ByteArrayInputStream(body.getBytes());
return
new
ServletInputStream() {
...略
};
}
@Override
public
BufferedReader getReader()
throws
IOException {
return
new
BufferedReader(
new
InputStreamReader(
this
.getInputStream()));
}
}
|
RequestReadUtils(网上抄的) 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
private
static
final
int
BUFFER_SIZE =
1024
*
8
;
public
static
String read(HttpServletRequest request)
throws
IOException {
BufferedReader bufferedReader = request.getReader();
for
(Enumeration<String> iterator = request.getHeaderNames(); iterator.hasMoreElements();) {
String type = iterator.nextElement();
System.out.println(type+
" = "
+request.getHeader(type));
}
System.out.println();
StringWriter writer =
new
StringWriter();
write(bufferedReader,writer);
return
writer.getBuffer().toString();
}
public
static
long
write(Reader reader,Writer writer)
throws
IOException {
return
write(reader, writer, BUFFER_SIZE);
}
public
static
long
write(Reader reader, Writer writer,
int
bufferSize)
throws
IOException
{
int
read;
long
total =
0
;
char
[] buf =
new
char
[bufferSize];
while
( ( read = reader.read(buf) ) != -
1
) {
writer.write(buf,
0
, read);
total += read;
}
return
total;
}
|
2.2 定义Filter 。
1
2
3
4
5
6
7
8
9
10
11
|
@WebFilter
public
class
TestFilter
implements
Filter{
@Override
public
void
doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain){
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
MyRequestWrapper wrapper = WebUtils.getNativeRequest(request, MyRequestWrapper.
class
);
chain.doFilter(wrapper ==
null
?
new
MyRequestWrapper(request) :wrapper,servletRequest);
}
}
|
使用的SpringBoot v2.1.x版本 。
1、Form提交无问题 。
2、获取RequestBody无问题 。
使用SpringBoot v2.2.0以上版本(包括v2.3.x) 。
1、Form提交无法获取参数 。
2、获取RequestBody无问题 。
经过排查,v2.2.x对比v2.1.x的不同在于一下代码差异:
1
2
3
4
5
6
7
|
BufferedReader bufferedReader = request.getReader();
-----------------
char
[] buf =
new
char
[bufferSize];
while
( ( read = reader.read(buf) ) != -
1
) {
writer.write(buf,
0
, read);
total += read;
}
|
当表单提交时 。
1、v2.1.x无法read到内容,读取结果为-1 。
2、v2.2.x、v2.3.x能够读取到内容 。
当表单提交时(x-www-form-urlencoded),inputStream读取一次后后续不会触发wrapper的getInputStream操作,所以Controller无法获取到参数.
解决方案 。
MyRequestWrapper改造 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
public
class
MyRequestWrapper
extends
HttpServletRequestWrapper {
private
final
String body;
public
MyRequestWrapper(HttpServletRequest request)
throws
IOException {
super
(request);
this
.body = getBodyString(request);
}
public
String getBody() {
return
body;
}
public
String getBodyString(
final
HttpServletRequest request)
throws
IOException {
String contentType = request.getContentType();
String bodyString =
""
;
StringBuilder sb =
new
StringBuilder();
if
(StringUtils.isNotBlank(contentType) && (contentType.contains(
"multipart/form-data"
) || contentType.contains(
"x-www-form-urlencoded"
))) {
Map<String, String[]> parameterMap = request.getParameterMap();
for
(Map.Entry<String, String[]> next : parameterMap.entrySet()) {
String[] values = next.getValue();
String value =
null
;
if
(values !=
null
) {
if
(values.length ==
1
) {
value = values[
0
];
}
else
{
value = Arrays.toString(values);
}
}
sb.append(next.getKey()).append(
"="
).append(value).append(
"&"
);
}
if
(sb.length() >
0
) {
bodyString = sb.toString().substring(
0
, sb.toString().length() -
1
);
}
return
bodyString;
}
else
{
return
IOUtils.toString(request.getInputStream());
}
}
@Override
public
ServletInputStream getInputStream()
throws
IOException {
final
ByteArrayInputStream bais =
new
ByteArrayInputStream(body.getBytes());
return
new
ServletInputStream() {
@Override
public
boolean
isFinished() {
return
false
;
}
@Override
public
boolean
isReady() {
return
false
;
}
@Override
public
int
read() {
return
bais.read();
}
@Override
public
void
setReadListener(ReadListener readListener) {
}
};
}
@Override
public
BufferedReader getReader()
throws
IOException {
return
new
BufferedReader(
new
InputStreamReader(
this
.getInputStream()));
}
}
|
以上为个人经验,希望能给大家一个参考,也希望大家多多支持我.
原文链接:https://blog.csdn.net/litlit023/article/details/111874579 。
最后此篇关于springboot接口如何多次获取request中的body内容的文章就讲到这里了,如果你想了解更多关于springboot接口如何多次获取request中的body内容的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在尝试在我的代码库中为我正在编写的游戏服务器更多地使用接口(interface),并了解高级概念以及何时应该使用接口(interface)(我认为)。在我的例子中,我使用它们将我的包相互分离,并使
我有一个名为 Widget 的接口(interface),它在我的整个项目中都在使用。但是,它也用作名为 Widget 的组件的 Prop 。 处理此问题的最佳方法是什么?我应该更改我的 Widget
有一个接口(interface)可以是多个接口(interface)之一 interface a {x:string} interface b {y:string} interface c {z:st
我遇到了一种情况,我需要调用第三方服务来获取一些信息。这些服务对于不同的客户可能会有所不同。我的界面中有一个身份验证功能,如下所示。 interface IServiceProvider { bool
在我的例子中,“RequestHandlerProxy”是一个结构,其字段为接口(interface)“IAdapter”,接口(interface)有可能被调用的方法,该方法的输入为结构“Reque
我有一个接口(interface)Interface1,它已由类A实现,并且设置了一些私有(private)变量值,并且我将类A的对象发送到下一个接受输入作为Interface2的类。那么我怎样才能将
假设我有这样的类和接口(interface)结构: interface IService {} interface IEmailService : IService { Task SendAs
有人知道我在哪里可以找到 XML-RPC 接口(interface)的定义(在 OpenERP 7 中)?我想知道创建或获取对象需要哪些参数和对象属性。每个元素的 XML 示例也将非常有帮助。 最佳答
最近,我一直在阅读有关接口(interface)是抽象的错误概念的文章。一篇这样的帖子是http://blog.ploeh.dk/2010/12/02/InterfacesAreNotAbstract
如果我有一个由第三方实现的现有 IInterface 后代,并且我想添加辅助例程,Delphi 是否提供了任何简单的方法来实现此目的,而无需手动重定向每个接口(interface)方法?也就是说,给定
我正在尝试将 Article 数组分配给我的 Mongoose 文档,但 Typescript 似乎不喜欢这样,我不知道为什么它显示此警告/错误,表明它不可分配. 我的 Mongoose 模式和接口(
我有两个接口(interface): public interface IController { void doSomething(IEntity thing); } public inte
是否可以创建一个扩展 Serializable 接口(interface)的接口(interface)? 如果是,那么扩展接口(interface)的行为是否会像 Serilizable 接口(int
我试图在两个存储之间创建一个中间层,它从存储 A 中获取数据,将其转换为相应类型的存储 B,然后存储它。由于我需要转换大约 50-100 种类型,我希望使用 map[string]func 并根据 s
我正在处理一个要求,其中我收到一个 JSON 对象,其中包含一个日期值作为字符串。我的任务是将 Date 对象存储在数据库中。 这种东西: {"start_date": "2019-05-29", "
我们的方法的目标是为我们现有的 DAO 和模型类引入接口(interface)。模型类由各种类型的资源 ID 标识,资源 ID 不仅仅是随机数,还带有语义和行为。因此,我们必须用对象而不是原始类型来表
Collection 接口(interface)有多个方法。 List 接口(interface)扩展了 Collection 接口(interface)。它声明与 Collection 接口(int
我有一个 Java 服务器应用程序,它使用 Jackson 使用反射 API 对 DTO 进行一般序列化。例如对于这个 DTO 接口(interface): package com.acme.libr
如果我在 Kotlin 中有一个接口(interface): interface KotlinInterface { val id: String } 我可以这样实现: class MyCla
我知道Java中所有访问修饰符之间的区别。然而,有人问了我一个非常有趣的问题,我很难找到答案:Java 中的 private 接口(interface)和 public 接口(interface)有什
我是一名优秀的程序员,十分优秀!