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或者普通的过滤器类只是将其包装了多层,通过反射一层一层剥开即可。

总结

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

相关文章

  • 三步轻松搭建springMVC框架

    三步轻松搭建springMVC框架

    这篇文章主要教大家三步轻松搭建springMVC框架,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • 在SpringBoot下读取自定义properties配置文件的方法

    在SpringBoot下读取自定义properties配置文件的方法

    这篇文章主要介绍了在SpringBoot下读取自定义properties配置文件的方法,文中涉及到了Spring-boot中读取config配置文件的两种方式,需要的朋友可以参考下
    2017-12-12
  • Springboot访问templates html页面过程详解

    Springboot访问templates html页面过程详解

    这篇文章主要介绍了Springboot访问templates html页面过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • Spring MVC项目中log4J和AOP使用详解

    Spring MVC项目中log4J和AOP使用详解

    项目日志记录是项目开发、运营必不可少的内容,有了它可以对系统有整体的把控,出现任何问题都有踪迹可寻。下面这篇文章主要给大家介绍了关于Spring MVC项目中log4J和AOP使用的相关资料,需要的朋友可以参考下。
    2017-12-12
  • SpringCloud整合OpenFeign问题

    SpringCloud整合OpenFeign问题

    这篇文章主要介绍了SpringCloud整合OpenFeign问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • Java如何生成4位、6位随机数短信验证码(高效实现)

    Java如何生成4位、6位随机数短信验证码(高效实现)

    这篇文章主要介绍了Java如何生成4位、6位随机数短信验证码(高效实现),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • Java深入浅出说流的使用

    Java深入浅出说流的使用

    这篇文章主要介绍了Java深入浅出说流的使用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • 详解SpringBoot restful api的单元测试

    详解SpringBoot restful api的单元测试

    本篇文章主要介绍了详解SpringBoot restful api的单元测试,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • Spring的@Transactional注解使用详细解析

    Spring的@Transactional注解使用详细解析

    这篇文章主要介绍了Spring的@Transactional注解使用详细解析,@Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功、要么同时失败,需要的朋友可以参考下
    2023-11-11
  • java 实现取int型的第二个字节的数

    java 实现取int型的第二个字节的数

    这篇文章主要介绍了java 实现取int型的第二个字节的数,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01

最新评论