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或者普通的过滤器类只是将其包装了多层,通过反射一层一层剥开即可。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。