- 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的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
这两个句子有什么区别: res = requests.request('POST', url) 和 res = requests.request.post(url) 最佳答案 它们几乎是一样的:htt
我正在使用“请求对话框”来创建 Facebook 请求。为了让用户收到请求,我需要使用图形 API 访问 Request 对象。我已经尝试了大多数看起来合适的权限设置(read_requests 和
urllib.request和http.client都是python标准库。前者相关方法的文档是 here后者,here (我使用的是3.5) 有谁知道为什么标准库中有两种方法看起来做同样的事情,或者
我是 Twisted 的新手,我不明白为什么在运行我的脚本时会出现此错误。\ 基本上,该脚本由 2 个页面组成,第一个页面是一个 HTML 表单,它调用自身执行一个阻塞方法并显示结果。当请求同时发送到
我有一个客户端 JS 文件,其中包含: agent = require('superagent'); request = agent.get(url); 然后我有类似的东西 request.get(u
提前输入功能可以正常工作。但问题是,提前输入功能会在每个数据请求上发出 JSON 请求,而实际上只应针对一个特定请求发生。 我有以下 Controller : #controllers/agencie
我正在使用 Rust 开发一个小型 API,我不确定如何在两个地方访问来自 Iron 的 Request。 Authentication 中间件为 token 读取一次Request,如果路径被允许(
问题起因 今天一位网友向我们反馈,用Chrome打开某些博客文章时,会出现"Bad Request - Request Too Long. HTTP Error 400. The siz
当我从 LinkedIn 向 https://api.linkedin.com/uas/oauth/requestToken 请求请求 token 时,出现以下错误: oauth_problem=si
我只是想使用 okhttp 下载一些字节数据,但在我完成代码之前,我遇到了一个问题,android studio 报告了一个错误,说“Request(okhttp3.Request.Builder)
我正在使用 Windows 10。我想在我的系统上使用 Angular 4。当我运行 node -v 和 npm -v 时,它会显示版本。但是当我执行语句 npm install -g @angula
我正在尝试让一个简单的 Iron 示例起作用: extern crate iron; extern crate router; use iron::prelude::*; use iron::stat
我正在尝试使用嵌套字典“动态”创建一个数据输入表单(目前,我使用具有 3 个值的数组,但将来数组中的元素数量可能会有所不同)。这似乎工作正常,并且表单“正确”渲染了 html 模板(正确 = 我看到了
从 ASP.NET 中的代码隐藏访问表单或查询字符串值时,使用的优缺点是什么,例如: // short way string p = Request["param"]; 代替: // long way
我遇到了一个问题,我想知道更好的解决方法。 有五个 api 请求并行运行,第二个请求依赖于第四个请求的响应,但所有 5 个请求都已在运行。什么是更好的方法? 需要建议。提前致谢。 最佳答案 调度地面工
我收到以下错误:TypeError:序列项 0:预期字节、字节数组或具有缓冲区接口(interface)的对象、找到元组 我检查了Python文档,urllib.request.Request的参数似
当我向函数添加超时参数时,我的代码总是进入异常并打印出“我失败了”。当我删除超时参数时,代码会正常工作,并进入 try 子句。关于超时参数如何在 urllib.request 函数中工作的任何信息?
我使用 cURL 向服务器发送请求这是链接:Server Side script for cURL request我用 file_get_contents('php://input'); 读取发送的数
请大家帮帮我我正在尝试使用 NUTCH 抓取网站,但它给我错误“java.io.IOException: Job failed!” 我正在运行此命令“bin/nutch solrindex http:
在我的 AngularJS 应用程序中,我无法弄清楚如何对 then promise 的执行更改 location.url 进行单元测试。我有一个函数,登录 ,调用服务,身份验证服务 .它返回 pro
我是一名优秀的程序员,十分优秀!