springgateway动态路由(Spring在Filter中记录Web请求Request和返回Response的内容及时长)
1 简介
在Spring MVC中 ,我们有时需要记录一下请求和返回的内容 ,方便出现问题时排查 。比较Header 、Request Body等 。这些在Controller也可以记录 ,但在Filter中会更方便 。而我们使用的是OncePerRequestFilter 。
2 记录请求
2.1 流重复读的问题
可以通过下面的代码来读取请求Body:
byte[] requestBody = StreamUtils.copyToByteArray(request.getInputStream()); log.info("request body = {}", new String(requestBody, StandardCharsets.UTF_8));但是这里从流读取了一次内容后 ,后续不可再读了 。这就造成了真正处理请求的时候 ,报错失败 ,我们需要把Request对象改造成可重复读的类 。
2.2 通过Wrapper解决流重复读的问题
为了可以让流重复读 ,加了以下Wrapper:
public class PkslowRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public PkslowRequestWrapper(HttpServletRequest request) throws IOException { super(request); body = StreamUtils.copyToByteArray(request.getInputStream()); } @Override public ServletInputStream getInputStream() throws IOException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return byteArrayInputStream.read(); } @Override public boolean isFinished() { return true; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) { } }; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } }这里主要在构造时读了流 ,然后存在变量body里 ,每次返回流的时候从body构造回去即可 。
在Filter中使用这个Wrapper如下:
PkslowRequestWrapper request = new PkslowRequestWrapper(req); ServletInputStream servletInputStream = request.getInputStream(); String body = StreamUtils.copyToString(servletInputStream, Charset.defaultCharset()); log.info("Request Body(PkslowRequestWrapper): {}", body);2.3 内置Filter
其实,针对Request ,Spring Boot提供了内置的Filter可以直接记录请求 ,使用如下:
package com.pkslow.springboot.common.web.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.CommonsRequestLoggingFilter; @Configuration public class PkslowConfig { @Bean public CommonsRequestLoggingFilter loggingFilter() { CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter(); filter.setIncludeHeaders(true); filter.setIncludeClientInfo(true); filter.setIncludePayload(true); filter.setIncludeQueryString(true); filter.setAfterMessagePrefix("CommonsRequestLoggingFilter Request: "); return filter; } }但要开debug级别的日志才会打出来 。
logging: level: root: debug日志如下:
DEBUG 20356 --- [nio-8080-exec-1] o.s.w.f.CommonsRequestLoggingFilter : Before request [POST /hello/pkslow, client=127.0.0.1, headers=[authorization:"Basic xxxxxx", content-length:"37", host:"localhost:8080", connection:"Keep-Alive", user-agent:"Apache-HttpClient/4.5.13 (Java/17.0.5)", accept-encoding:"gzip,deflate", Content-Type:"application/json;charset=UTF-8"]]3 记录返回
返回也是一样,有流不可重复读的问题 ,使用Spring自带的ContentCachingResponseWrapper即可 。
ContentCachingResponseWrapper response = new ContentCachingResponseWrapper(res); log.info("Response Code: {}", response.getStatus()); String responseBody = new String(response.getContentAsByteArray(), response.getCharacterEncoding()); log.info("Response Body: {}", responseBody); response.copyBodyToResponse();特别注意一定要调用copyBodyToResponse()这个方法 ,不然无法返回body给请求端了。
4 记录时间
记录整个请求的处理时间请参考: Java如何测量方法执行时间
5 测试
测试一下:
POST http://localhost:8080/hello/pkslow Content-Type: application/json Authorization: Basic xxxxxx { "id": 999, "value": "content" }执行日志结果如下:
6 总结
也可使用ContentCachingRequestWrapper来解决请求流不可重复读的问题,但这个Wrapper是有限制的 ,具体可以看它源码 。也有人提了Issue 。
代码请看GitHub: https://github.com/LarryDpk/pkslow-samples
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!