Springboot应用中过滤器如何修改response的header和body内容

 更新时间:2023年07月19日 10:06:08   作者:彼岸花@开  
这篇文章主要介绍了Springboot应用中过滤器如何修改response的header和body内容问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

Springboot过滤器修改response的header和body内容

springboot添加过滤器,继承Filter接口,实现doFilter方法

方法一,实现类增加注解@WebFilter,注解参数filterName表示过滤器名称,urlPatterns表示要过滤的url路径,在启动类增加注解@ServletComponentScan,表示能扫描到该类。

当有多个过滤器时,通过注解@Order,注解参数大小表示过滤器执行的县厚顺序,越小越先执行

@WebFilter(filterName = "responseFilter",urlPatterns = "/*")
public class ResponseFilter implements Filter

方法二,创建新配置类,添加 @Configuration 注解,将自定义Filter加入过滤链。

@Configuration
public class DefinedFilterConfig {
    @Bean
    public FilterRegistrationBean responseFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        ResponseFilter responseFilter = new ResponseFilter();
        filterRegistrationBean.setFilter(responseFilter);
        filterRegistrationBean.addUrlPatterns("/*");//配置过滤规则
        filterRegistrationBean.setName("responseFilter");//设置过滤器名称
        filterRegistrationBean.setOrder(1);//执行次序
        return filterRegistrationBean;
    }
}

springboot在response中添加或者修改header

分两种情况

  • 情况1,在chain.doFilter(servletRequest, servletResponse)代码之前
servletResponse.addHeader("XXX", "xxxx");

或者

servletResponse.setHeader("XXX", "xxxx");

两者的区别是addHeader不会覆盖,只会追加,会照成headerName重名

setHeader会覆盖重名的headerName

这样是可以添加成功

  • 情况2,在chain.doFilter(servletRequest, servletResponse)代码之后

添加addHeader或者serHeader无效,是因为这和过滤器的处理流程以及对header的处理时机有关

首先过滤器链的处理流程是:进入到一个过滤器的doFitler方法中,处理一些逻辑,然后调用chain.doFilter(request, httpServletResponse);进入到过滤器链的下一个过滤器的doFilter方法中.当在过滤器链上最后一个过滤器的doFilter方法中调用chain.doFilter(request, httpServletResponse);时,将会把请求转发到Servlet中,再分配到对应的Controller的方法中。当从Controller的方法中退出,再回到最后一个过滤器的doFilter方法中之前,就将会把respone对象上的header写入到headerBuffer中。

所以,在chain.doFilter()方法之后,一方面是给response对象设置header不会成功,因为发现response对象的状态已经是commited状态,就不会再写入到headers里,另一方面,即便通过反射的方式写入了,也不会输出给客户端,因为headers已经处理过了。

springboot在response中修改body

spring提供了HttpServletResponse的包装类HttpServletResponseWrapper

首先需要继承该包装类,生成自定义包装类BodyCachingHttpServletResponseWrapper

public class BodyCachingHttpServletResponseWrapper extends HttpServletResponseWrapper {
    private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    private HttpServletResponse response;
    private PrintWriter pwrite;
    public BodyCachingHttpServletResponseWrapper(HttpServletResponse response) {
        super(response);
        this.response = response;
    }
    public byte[] getBytes() {
        if(pwrite != null) {
            pwrite.close();
            return byteArrayOutputStream.toByteArray();
        }
        if(byteArrayOutputStream != null) {
            try {
                byteArrayOutputStream.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return byteArrayOutputStream.toByteArray();
    }
    @Override
    public ServletOutputStream getOutputStream() {
        return new ServletOutputStreamWrapper(this.byteArrayOutputStream , this.response);
    }
    @Override
    public PrintWriter getWriter() throws IOException {
        pwrite = new PrintWriter(new OutputStreamWriter(this.byteArrayOutputStream , this.response.getCharacterEncoding()));
        return pwrite;
    }
    private static class ServletOutputStreamWrapper extends ServletOutputStream {
        private ByteArrayOutputStream outputStream;
        private HttpServletResponse response;
        public ServletOutputStreamWrapper(ByteArrayOutputStream outputStream, HttpServletResponse response) {
            super();
            this.outputStream = outputStream;
            this.response = response;
        }
        @Override
        public boolean isReady() {
            return true;
        }
        @Override
        public void setWriteListener(WriteListener listener) {
        }
        @Override
        public void write(int b) throws IOException {
            this.outputStream.write(b);
        }
//        @Override
//        public void flush() throws IOException {
//            if (! this.response.isCommitted()) {
//                byte[] body = this.outputStream.toByteArray();
//                ServletOutputStream outputStream = this.response.getOutputStream();
//                outputStream.write(body);
//                outputStream.flush();
//            }
//        }
    }

然后通过过滤器,将自定义包装类BodyCachingHttpServletResponseWrappe传递到chain.doFilter(servletRequest, bodyCachingHttpServletResponseWrapper ),通过bodyCachingHttpServletResponseWrapper .getBytes()方式获得原有body的内容。

最后自己根据自己的逻辑代码,修改原有body的内容,重新写入输出流即可

public class ResponseFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        BodyCachingHttpServletResponseWrapper responseWrapper = new BodyCachingHttpServletResponseWrapper((HttpServletResponse) servletResponse);
        //获取当前accessToken
        Optional<String> currentUserAccessTokenOptional = SecurityUtils.getCurrentUserAccessToken();
        String currentUserAccessToken = null;
        if(currentUserAccessTokenOptional.isPresent()) {
            currentUserAccessToken = currentUserAccessTokenOptional.get();
        }
        responseWrapper.addHeader("xuncai_access_token", currentUserAccessToken);
        chain.doFilter(servletRequest, responseWrapper);
        Optional<String> xTotalCount = Optional.ofNullable(responseWrapper.getHeader("X-Total-Count"));
        byte[] writeValueByte = null;
        if(xTotalCount.isPresent() && responseWrapper.getBytes() != null) {
            String currentTotal = xTotalCount.get();
            String currentData = new String(responseWrapper.getBytes());
            String writeValue = "{\"total\":"+currentTotal+",\"data\":"+currentData+"}";
            writeValueByte = writeValue.getBytes();
        }else {
            writeValueByte = responseWrapper.getBytes();
        }
        //重新写入输出流
        ServletOutputStream outputStream = servletResponse.getOutputStream();
        outputStream.write(writeValueByte);
        outputStream.flush();
        outputStream.close();
    }
}

经过测试,一切OK。

Springboot在绝大部分过滤器中修改请求头信息

实现方式

反射。

代码

package one.util;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.RequestFacade;
import org.apache.tomcat.util.http.MimeHeaders;
import org.springframework.util.ObjectUtils;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class HeadersEdit {
    private final HttpServletRequest request;
    private Map<String, String> modifyHeaders;
    private MimeHeaders itsHeaders;
    private boolean isDeleteKnown=true;
    public HeadersEdit(HttpServletRequest request){
        this.request=request;
        modifyHeaders= new HashMap<>();
        itsHeaders=getMimeHeaders(getRequestFacade());
    }
    private HttpServletRequest getRequestFacade(){
        try{
            Field field= ServletRequestWrapper.class.getDeclaredField("request");
            HttpServletRequest result;
            field.setAccessible(true);
            result=(HttpServletRequest)field.get((request));
            while(!(result instanceof RequestFacade)){
                result=(HttpServletRequest)field.get((result));
            }
            return result;
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("HeadersEdit Error!");
        }
    }
    private MimeHeaders getMimeHeaders(HttpServletRequest requestFacade){
        try{
            Field itsRequest= RequestFacade.class.getDeclaredField("request");
            itsRequest.setAccessible(true);
            HttpServletRequest o5=(HttpServletRequest) itsRequest.get(requestFacade);
            Field coyoteField= Request.class.getDeclaredField("coyoteRequest");
            coyoteField.setAccessible(true);
            org.apache.coyote.Request coyoteRequest=(org.apache.coyote.Request) coyoteField.get(o5);
            Field headers=org.apache.coyote.Request.class.getDeclaredField("headers");
            headers.setAccessible(true);
            return (MimeHeaders)headers.get(coyoteRequest);
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("HeadersEdit Error!");
        }
    }
    private boolean setValueExecutor(Map<String,String>newHeaders){
        String key;
        for(Map.Entry<String, String> entry : newHeaders.entrySet()){
            key=entry.getKey();
            if(itsHeaders.getHeader(key)!=null){
                if(isDeleteKnown){
                    itsHeaders.removeHeader(key);
                }else{
                    return false;
                }
            }
            itsHeaders.addValue(key).setString(entry.getValue());
        }
        return true;
    }
    public boolean setValue(String key,String value){
        if(ObjectUtils.isEmpty(key)||ObjectUtils.isEmpty(value)){
            return false;
        }
        Map<String,String>map=new HashMap<>();
        map.put(key,value);
        return setValueExecutor(map);
    }
    public boolean setValue(Map<String,String>newHeaders){
        if(ObjectUtils.isEmpty(newHeaders)){
            return false;
        }
        return setValueExecutor(newHeaders);
    }
    public void changeCheck(){
        isDeleteKnown=!isDeleteKnown;
    }
}

测试环境

springsecurity中的过滤器、以及普通的过滤器。

原理简述

许多过滤器都间接继承了ServletRequestWrapper类,它有个属性叫request。

springsecurity或者普通的过滤器类只是将其包装了多层,通过反射一层一层剥开即可。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot定制JSON响应数据返回的示例代码

    SpringBoot定制JSON响应数据返回的示例代码

    @JsonView 是 Jackson 库中的一个注解,它允许你定义哪些属性应该被序列化到 JSON 中,基于不同的“视图”或“配置”,在本文中,通过了解@JsonView,你将能够更好地掌握如何在Spring Boot应用中定制JSON数据的输出,需要的朋友可以参考下
    2024-05-05
  • 深入解析kafka 架构原理

    深入解析kafka 架构原理

    Kafka使用领域非常广泛,在大数据时代kafka使用真香,LinkedIn、Microsoft和Netflix每天都用Kafka处理万亿级的信息。本文就让我们一起来大白话kafka的架构原理,感兴趣的朋友一起看看吧
    2021-11-11
  • SpringBoot 使用Prometheus采集自定义指标数据的方案

    SpringBoot 使用Prometheus采集自定义指标数据的方案

    这篇文章主要介绍了SpringBoot 使用Prometheus采集自定义指标数据,我们在k8s集群成功搭建了Prometheus服务,今天,我们将在springboot2.x中使用prometheus记录指标,需要的朋友可以参考下
    2022-10-10
  • SparkSQL读取hive数据本地idea运行的方法详解

    SparkSQL读取hive数据本地idea运行的方法详解

    这篇文章主要介绍了SparkSQL读取hive数据本地idea运行的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • java实现爬取知乎用户基本信息

    java实现爬取知乎用户基本信息

    这篇文章主要为大家介绍了一个基于JAVA的知乎爬虫,抓取知乎用户基本信息,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • java书店系统毕业设计 总体设计(1)

    java书店系统毕业设计 总体设计(1)

    这篇文章主要介绍了java书店系统毕业设计,第一步系统总体设计,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-10-10
  • 一文搞懂Java常见的三种代理模式(静态代理、动态代理和cglib代理)

    一文搞懂Java常见的三种代理模式(静态代理、动态代理和cglib代理)

    Java中常见的三种代理模式是静态代理模式、动态代理模式和CGLIB代理模式,本文就来给大家详细的讲解一下这三种代理模式,感兴趣的小伙伴跟着小编一起来看看吧
    2023-08-08
  • SpringBoot整合Caffeine实现本地缓存的实践分享

    SpringBoot整合Caffeine实现本地缓存的实践分享

    缓存是提升系统性能的一个不可或缺的工具,通过缓存可以避免大部分重复的请求到数据库层,减少IO链接次数,提升整体的响应速率,本地缓存中比较常见的比如 Caffeine 缓存,这篇文章将结合具体的 Springboot 项目搭配 Caffeine 实现本地缓存的各种使用方式
    2024-07-07
  • SpringBoot中Date格式化处理的三种实现

    SpringBoot中Date格式化处理的三种实现

    Spring Boot作为一个简化Spring应用开发的框架,提供了多种处理日期格式化的方法,本文主要介绍了SpringBoot中Date格式化处理实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • Spring2.5.6开发环境搭建图文教程

    Spring2.5.6开发环境搭建图文教程

    这篇文章主要为大家详细介绍了Spring2.5.6开发环境搭建图文教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05

最新评论