首页 > 其他分享 >Spring在Filter中记录Web请求Request和返回Response的内容及时长

Spring在Filter中记录Web请求Request和返回Response的内容及时长

时间:2023-02-06 20:22:42浏览次数:57  
标签:body Web Spring Request request filter new public

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

标签:body,Web,Spring,Request,request,filter,new,public
From: https://www.cnblogs.com/larrydpk/p/17096601.html

相关文章

  • webrtc 自定义对接摄像机视频流
    ​​https://blog.csdn.net/u013113491/article/details/80285181​​编码器伪装法​​https://blog.csdn.net/foruok/article/details/70237019​​众所周知浏览器不支持......
  • SpringBoot响应Json数据乱码通过配置解决
    场景实现把SpringBoot的response编码设置为utf-8找到application.properties配置文件添加如下:#设置响应为utf-8spring.http.encoding.force-response=true 再次刷新浏览器......
  • SpringBoot中自定义消息转化器
    场景1.SpringBoot自动配置了消息转化器。2.自定义消息转化器,只需要在类中添加消息转化器的@Bean,就会被SpringBoot自动加入到容器中。实现新建Controllerpackagecom.exampl......
  • SpringBoot+MyBatis的动态SQL、使用动态SQL时List传值错误解决方案
    目录实现动态SQL的四种方式:1、XML配置2、脚本SQL3、在方法中构建SQL4、结构化SQL关于动态SQL的List传值错误问题1、错误代码2、解决错误实现动态SQL的四种方式:1、XML配置......
  • Python requests.Session 协程 下载文件
    Pythonrequests.Session协程下载文件 #coding:utf-8fromgeventimportmonkeymonkey.patch_all()fromgevent.poolimportPoolimportgeventimportrequ......
  • Spring Cloud Alibaba 在 Proxyless Mesh 上的探索
    站在2023年的今天,ServiceMesh早已不是一个新兴的概念,回顾过去6年多的发展历程,ServiceMesh从一经推出就受到来自全世界的主流技术公司关注和追捧。2016年作为S......
  • spring 重复注解和aop拦截的实现示例
    前言:1:jdk1.8开始支持重复注解@Repeatable实现2:aop拦截需要拦截当前注解和@Repeatable指向的包装注解才可以完全拦截到,因为:1.当在在方法上只有一个注解时,aop拦截......
  • 详解Spring AOP自定义可重复注解没有生效问题
    目录1.问题背景2.不啰嗦,上代码3.问题排查3.1是不是切点写得有问题,于是换成如下形式:3.2是不是使用的地方不是代理对象4.问题原因 1.问题背景工作中遇......
  • Spring AOP使用接口方式实现
    目录一.环境准备二、spring接口方式实现aop步骤1.业务接口实现2.业务类3.通知类4.自定义切##点5.配置xml文件6.方法入口三.分析Spring提供了很多的......
  • 若依前后端分离版怎样修改主页面显示请求的SpringBoot后台数据
    场景使用若依的前后端分离版,本来的首页效果是 现在如果要根据具体业务实现从后台获取要显示的数据实现类似下面的效果 注:霸道的程序猿获取编程相关电子书、教程推送与免......