详解在Spring MVC或Spring Boot中使用Filter打印请求参数问题

网友投稿 792 2022-12-08


详解在Spring MVC或Spring Boot中使用Filter打印请求参数问题

使用Spring MVC或Spring Boot中打印或记录日志一般使用AOP记录Request请求和Response响应参数,在不使用AOP的前提下,如果在Filter中打印日志,在打印或消费请求类型为Content-Type:application/json的请求时,会出现严重的问题。

在Spring体系中,过滤器的定义我们一般采用继承OncePerRequestFilter的方式,当然也可以使用原始的Filter。

错误写法一:

如果不对request和response进行处理,使用伪代码采用如下写法打印请求和响应参数(注:此时request请求类型为Post,接收的是Json数据)

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse

response, FilterChain filterChain) throws ServletException, IOException {

filterChain.doFilter(request, response);

printRequestLog(request);

printResonseLog(response);

}

运行测试后你会发现抛出如下异常:

java.io.IOException: Stream closed

 at org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:359) ~[tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:132) ~[tomcat-embed-core-9.0.31.jar:9.0.31]

 at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284) ~[na:1.8.0_191]

 at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326) ~[na:1.8.0_191]

 at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178) ~[na:1.8.0_191]

 at java.io.InputStreamReader.read(InputStreamReader.java:184) ~[na:1.8.0_191]

 at java.io.BufferedReader.fill(BufferedReader.java:161) ~[na:1.8.0_191]

 at java.io.BufferedReader.readLine(BufferedReader.java:324) ~[na:1.8.0_191]

 at java.io.BufferedReader.readLine(BufferedReader.java:389) ~[na:1.8.0_191]

 at com.micro.backend.filter.LoggingFilter.getBodyString(LoggingFilter.java:60) [classes/:na]

 at com.micro.backend.filter.LoggingFilter.doFilterInternal(LoggingFilter.java:49) [classes/:na]

 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]

 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]

 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]

 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]

 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]

 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:94) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]

 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]

 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]

 at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.1.14.RELEASE.jar:5.1.14.RELEASE]

 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1639) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_191]

 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_191]

 at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.31.jar:9.0.31]

 at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]

错误写法二:

如果不对request和response进行处理,使用伪代码采用如下写法打印请求和响应参数(注:此时request请求类型为Post,接收的是Json数据)

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse

response, FilterChain filterChain) throws ServletException, IOException {

printRequestLog(request);

printResonseLog(response);

filterChain.doFilter(request, response);

}

运行测试后你会发现抛出如下异常:

org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing

遇到这样的问题你是不是有坐立不安、心烦意乱、百爪挠心的痛楚,不要着急,下面我给出一个解决方案。

首先我们使用装饰器模式,创建request和response两个包装类,如下:

import javax.servlet.ReadListener;

import javax.servlet.ServletInputStream;

import javax.servlet.ServletRequest;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletRequestWrapper;

import java.io.*;

import java.nio.charset.Charset;

/**

* @Description: 请求包装器

* @Author: liuliya

* @CreateDate: 2020/4/29 10:00

*/

public class RequestWrapper extends HttpServletRequestWrapper {

private final byte[] body;

public RequestWrapper(HttpServletRequest request) throws IOException {

super(request);

body = getRequestBodyString(request).getBytes(Charset.defaultCharset());

}

@Override

public BufferedReader getReader() throws IOException {

return new BufferedReader(new InputStreamReader(getInputStream()));

}

@Override

public ServletInputStream getInputStream() throws IOException {

final ByteArrayInputStream bais = new ByteArrayInputStream(body);

return new ServletInputStream() {

@Override

public int read() throws IOException {

return bais.read();

}

@Override

public boolean isFinished() {

return false;

}

@Override

public boolean isReady() {

return false;

}

@Override

public void setReadListener(ReadListener listener) {

}

};

}

public String getRequestBodyString(ServletRequest request) {

StringBuilder sb = new StringBuilder();

InputStream inputStream = null;

BufferedReader reader = null;

try {

inputStream = request.getInputStream();

reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));

String line;

while ((line = reader.readLine()) != null) {

sb.append(line);

}

} catch (IOException e) {

e.printStackTrace();

} finally {

if (inputStream != null) {

try {

inputStream.close();

} catch (IOException e) {

e.printStackTrace();

}

}

if (reader != null) {

try {

reader.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

return sb.toString(okEjbiFpy);

}

}

package com.micro.backend.filter.support;

import org.apache.commons.io.output.TeeOutputStream;

import javax.servlet.ServletOutputStream;

import javax.servlet.ServletResponse;

import javax.servlet.WriteListener;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpServletResponseWrapper;

import java.io.ByteArrayOutputStream;

import java.io.IOException;

import java.io.PrintWriter;

/**

* @Description: 响应包装器

* @Author: liuliya

* @CreateDate: 2020/4/29 10:00

*/

public class ResponseWrapper extends HttpServletResponseWrapper {

private final ByteArrayOutputStream bos = new ByteArrayOutputStream();

private PrintWriter writer = new PrintWriter(bos);

public ResponseWrapper(HttpServletResponse response) {

super(response);

}

@Override

public ServletResponse getResponse() {

return this;

}

@Override

public ServletOutputStream getOutputStream() throws IOException {

return new ServletOutputStream() {

@Override

public boolean isReady() {

return false;

}

@Override

public void setWriteListener(WriteListener listener) {

}

private TeeOutputStream tee = new TeeOutputStream(ResponseWrapper.super.getOutputStream(), bos);

@Override

public void write(int b) throws IOException {

tee.write(b);

}

};

}

@Override

public PrintWriter getWriter() throws IOException {

return new TeePrintWriter(super.getWriter(), writer);

}

public byte[] toByteArray() {

return bos.toByteArray();

}

}

package com.micro.backend.filter.support;

import java.io.PrintWriter;

//PrintWriter是一种写入字符的一种操作类,可以写入字符,TeePrintWriter继承了他,主要功能是把原始的字符流copy到branch里面。

public class TeePrintWriter extends PrintWriter {

PrintWriter branch;

public TeePrintWriter(PrintWriter main, PrintWriter branch) {

super(main, true);

this.branch = branch;

}

public void write(char buf[], int off, int len) {

super.write(buf, off, len);

super.flush();

branch.write(buf, off, len);

branch.flush();

}

public void write(String s, int off, int len) {

super.write(s, off, len);

super.flush();

branch.write(s, off, len);

branch.flush();

}

public void write(int c) {

super.write(c);

super.flush();

branch.write(c);

branch.flush();

}

public void flush() {

super.flush();

branch.flush();

}

}

接下来创建最重要的LoggingFilter类,继承OncePerRequestFilter,或者直接继承Servlet中原始的Filter。

package com.micro.backend.filter;

import com.micro.backend.filter.support.RequestWrapper;

import com.micro.backend.filter.support.ResponseWrapper;

import lombok.extern.slf4j.Slf4j;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

import java.io.IOException;

import java.io.UnsupportedEncodingException;

/**

* @Author: liuliya

* @CreateDate: 2020/4/28 23:30

*/

@Slf4j

@Configuration

public class LoggingFilter extends OncePerRequestFilter {

private static final String REQUEST_PREFIX_NAME = "Request请求: ";

private static final String RESPONSE_PREFIX_NAME = "Response请求: ";

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

request = new RequestWrapper(request);

response = new ResponseWrapper(response);

filterChain.doFilter(request, response);

printRequestLog(request);

printResponseLog((ResponseWrapper) response);

}

private void printRequestLog(final HttpServletRequest request) {

StringBuilder msg = new StringBuilder();

msg.append(REQUEST_PREFIX_NAME);

HttpSession session = request.getSession(false);

if (session != null) {

msg.append("sessionId = ").append(session.getId()).append("; ");

}

if (request.getMethod() != null) {

msg.append("method = ").append(request.getMethod()).append("; ");

}

if (request.getContentType() != null) {

msg.append("contentType = ").append(request.getContentType()).append("; ");

}

msg.append("uri = ").append(request.getRequestURI());

if (request.getQueryString() != null) {

msg.append('?').append(request.getQueryString());

}

ihttp://f (request instanceof RequestWrapper && !isMultipart(request) && !isBinaryContent(request)) {

RequestWrapper requestWrapper = (RequestWrapper) request;

msg.append("; payload = ").append(requestWrapper.getRequestBodyString(request));

}

log.info(msg.toString());

}

private boolean isBinaryContent(final HttpServletRequest request) {

if (request.getContentType() == null) {

return false;

}

return request.getContentType().startsWith("image")

|| request.getContentType().startsWith("video")

|| request.getContentType().startsWith("audio");

}

private boolean isMultipart(final HttpServletRequest request) {

return request.getContentType() != null

&& request.getContentType().startsWith("multipart/form-data");

}

private void printResponseLog(final ResponseWrapper response) {

StringBuilder msg = new StringBuilder();

msg.append(RESPONSE_PREFIX_NAME);

try {

msg.append("; payload = ")

.append(new String(response.toByteArray(), response.getCharacterEncoding()));

} catch (UnsupportedEncodingException e) {

log.warn("Failed to parse response payload", e);

}

log.info(msg.toString());

}

}

参考以上我整理出的代码,你就会发现奇迹!!!

为什么要这么写呢,其本质是把请求流拷贝了一份,一个供filterChain向下传递,一个来做流的消费,再有一个就是运用装饰器模式的精髓所在。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:带有@Transactional和@Async的循环依赖问题的解决
下一篇:Maven搭建springboot项目的方法步骤
相关文章

 发表评论

暂时没有评论,来抢沙发吧~