通过Filter实现修改http请求、响应

请求头和请求参数是不能直接修改,在servlet或者Controller中获取参数本质上是通过 getParameter(String name) 或者 public String[] getParameterValues(String name) 方法,但无论是ServletRequest还是HttpServletRequest都没提供修改请求头、请求参数方法(没有setHeader、setParameter方法,只提供了setAttribute方法);

getParameterMap()返回的时一个不可修改的map,所以也无法通过该方法来修改请求参数。

那到底该如何做才能实现呢?答案:通过装饰模式

一、修改请求

1、ServletRequestWrapper介绍:

简单介绍下ServletRequest、HttpServletRequest、ServletRequestWrapper以及HttpServletRequestWrapper他们之间的层次关系:

简单画一下UML图:

上面这个关系毫无疑问是一个很标准的装饰模式:

  • ServletRequest:抽象组件
  • HttpServletRequest:抽象组件的一个子类,它的实例被称做“被装饰者”ide
  • ServletRequestWrapper:一个基本的装饰类,这里是非抽象的工具
  • HttpServletRequestWrapper:一个具体的装饰者,固然这里也继承了HttpServletRequest这个接口,是为了获取一些在ServletRequest中没有的方法
  • ModifyParametersWrapper:一样是 一个具体的装饰者(自定义的一个类)

注:一个标准的装饰模式的UML类图是这样的:

2、Filter中修改请求参数、请求头原理:

在servlet、Controller中处理request的对象是HttpServletRequest接口实现类,HttpServletRequestWrapper类实现类该接口;我们可以利用装饰模式,创建一个装饰类继承HttpServletRequestWrapper,然后重写其中关于请求参数、请求头的方法,最后在Filter中将装饰类再传递下去,这样servlet中就可以通过getParameter方法获取被修改过的请求参数来。

  • 请求参数相关的方法:String getQueryString()、Map<String, String[]> getParameterMap()、String[] getParameterValues(String name)、String getParameter(String name)、Enumeration<String> getParameterNames()
  • 请求头相关的方法:String getHeader(String name)、Enumeration<String> getHeaderNames()、Enumeration<String> getHeaders(String name)

同理修改请求的cookie也是一样的道理。

3、示例(修改请求头、请求体):

1)定义MyRequestWrapper

public class MyRequestWrapper extends HttpServletRequestWrapper  {
    private Map<String,String[]> paramsMap;
    private Map<String, String> headerMap;
    public MyRequestWrapper(HttpServletRequest request) {
        super(request);
        
        this.paramsMap = new HashMap<>();
        this.paramsMap.putAll(request.getParameterMap());

        this.headerMap = new HashMap<>();
    }
    
    public void addParameter(String name,String value) {
        String[] parameterValues = getParameterValues(name);
        if (parameterValues == null) {
            this.paramsMap.put(name, new String[] {value});
        } else {
            parameterValues = Arrays.copyOf(parameterValues, parameterValues.length + 1);
            parameterValues[parameterValues.length-1] = value;
            this.paramsMap.put(name, parameterValues);
        }
    }
    
    @Override
    public Enumeration<String> getParameterNames() {
        Vector<String> vector = new Vector<String>(paramsMap.keySet());
        return vector.elements();
    }
    @Override
    public String getParameter(String name) {
         if(paramsMap.containsKey(name)) {
             return paramsMap.get(name).length > 0 ? paramsMap.get(name)[0] : null;
         }
         return null;
    }
    @Override
    public String[] getParameterValues(String name) {
         return paramsMap.get(name);
    }
    @Override
    public Map<String, String[]> getParameterMap() {
          return Collections.unmodifiableMap(paramsMap);
    }
    @Override
    public String getQueryString() {
        StringBuilder sb = new StringBuilder();
        if(paramsMap != null) {
            for(Map.Entry<String, String[]> en : paramsMap.entrySet()) {
                sb.append(en.getKey()).append("=").append(en.getValue()[0]).append("&");
            }
        }
        int len = sb.length();
        if(len > 0) {
            sb.deleteCharAt(len-1);
        }
        return sb.toString();
    }
    
    
    public void addHeader(String name, String value){
        this.headerMap.put(name, value);
    }
    @Override
    public String getHeader(String name) {
        String headerValue = headerMap.get(name);
        if (headerValue != null){
            return headerValue;
        }
        return super.getHeader(name);
    }
 
    @Override
    public Enumeration<String> getHeaderNames() {
        List<String> names = Collections.list(super.getHeaderNames());
        for (String name : headerMap.keySet()) {
            names.add(name);
        }
        return Collections.enumeration(names);
    }
 
    @Override
    public Enumeration<String> getHeaders(String name) {
        List<String> values = Collections.list(super.getHeaders(name));
        if (headerMap.containsKey(name)) {
            values.add(headerMap.get(name));
        }
        return Collections.enumeration(values);
    }
}

2)Filter:

public class MyFilter implements Filter {
    private FilterConfig filterConfig;
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        System.out.println("filter init...");
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        MyRequestWrapper requestWrapper = new MyRequestWrapper((HttpServletRequest)request);
        
        modifyParameter(requestWrapper,request);
        modifyHeader(requestWrapper,request);
        
        chain.doFilter(requestWrapper, response);
    }
    
    //修改请求参数
    private void modifyParameter(MyRequestWrapper requestWrapper,ServletRequest request) {
        requestWrapper.addParameter("myParam", "test");
    }
    //修改请求头
    private void modifyHeader(MyRequestWrapper requestWrapper,ServletRequest request) {
        requestWrapper.addHeader("myHead", "test");
    }
    
    @Override
    public void destroy() {
        System.out.println("filter destory...");
    }
}

3)配置Filter:

  <filter>
    <filter-name>myFilter</filter-name>
    <filter-class>cn.edu.nuc.springmvc_test.filter.MyFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>myFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

4)测试:

在servlet或者Controller中可以通过如下方式获取修改的参数、请求头

String header = request.getHeader("myHead");
String param = request.getParameter("myParam");

4、HttpServletRequest的输入流只能读取一次的问题:

对于Post的请求,我们可以通过调用request.getInputStream()获取请求体,将其转换成String,进而获取Post请求数据。对于上传文件,也是一个道理。

但是需要注意一点:在一次请求中,无论是在Filter、Servlet还是Controller中,调用request.getInputStream()方法获取请求体数据流,之后再次调用就无法获取请求体数据流类。

1)原因:

InputStream的read()方法内部有一个postion,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()会返回-1,表示已经读取完了。如果想要重新读取则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true。

request.getInputStream()方法返回的类型是ServletInputStream,它继承于InputStream。InputStream默认不实现reset(),并且markSupported()默认也是返回false;ServletInputStream该类也没有重写mark(),reset()以及markSupported()方法。

所以,request.getInputStream()无法重复读取流,这就是我们从request对象中获取的输入流就只能读取一次的原因。

2)解决:

最简单的方法:在请求最开始,将请求流数据保存到一个byte数组中,在同一个request中的后续操作直接从byte数组中获取数据即可。

实现:使用装饰模式,借助Filter可以简单来实现。

A、定义MyRequestWrapper2:

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class MyRequestWrapper2 extends HttpServletRequestWrapper  {
    private  byte[] body;
    
    public MyRequestWrapper2(HttpServletRequest request) {
        super(request);
        try {
            body = read((InputStream)request.getInputStream());
        } catch (Exception e) {
        }
    }
    
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return inputStream.read();
            }
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener listener) {
                
            }
        };
    }
    
    private byte[] read(InputStream inStream)throws Exception{
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while((len=inStream.read(buffer))!=-1){
            outputStream.write(buffer, 0, len);
        }
        inStream.close();
        return outputStream.toByteArray();
    }
}

说明:重写getReader和getInputStream方法。此外,这里使用了动态字节数组ByteArrayOutputStream和ByteArrayInputStream。

B、Filter:

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        MyRequestWrapper2 requestWrapper = new MyRequestWrapper2((HttpServletRequest)request);
        
        chain.doFilter(requestWrapper, response);
    }

C、测试:

private String inputStream2String(InputStream inputStream) {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (reader != null) {
                try {reader.close();} catch (IOException e) {}
            }
        }
        return sb.toString();
    }

@RequestMapping(value="/test4")
@ResponseBody
public Map<String,String> test4(HttpServletRequest request,HttpServletResponse httpResponse) {
        Map<String ,String> res = new HashMap<>();
        
        //stream
        try {
            ServletInputStream inputStream = request.getInputStream();
            String inputStream2String = inputStream2String(inputStream);
            System.out.println(inputStream2String);
            
            ServletInputStream inputStream2 = request.getInputStream();
            String inputStream2String2 = inputStream2String(inputStream2);
            System.out.println(inputStream2String2);
        } catch (IOException e) {
            e.printStackTrace();
        }
}

 

二、修改响应

有时我们希望在请求输出之前对response对象进行一些额外的操作,最后再发往客户端。同HttpServletRequest,HttpServletResponse对象没有Buffer功能,且只能读取一次,要实现修改响应,只能通过装饰模式,继承HttpServletResponseWrapper类来达到。

继承HttpServletResponseWrapper都需要重写那些方法呢?

  1. 以流的方式获取输出——重写getOutputStream() //返回一般为打印流,其底层是对ServletOutputStream引用。
  2. 以字符方式获取输出——重写getWriter() //直接对ServletOutputStream的引用
  3. 刷新流——重写flushBuffer()
  4. 重置流——重写reset()

示例:

1)定义response包装器WapperedResponse继承HttpServletResponseWrapper

package cn.edu.nuc.springmvc_test.filter;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

public class MyResponseWrapper extends HttpServletResponseWrapper {
    private ByteArrayOutputStream buffer = null;
    private ServletOutputStream out = null;
    private PrintWriter writer = null;

    public MyResponseWrapper(HttpServletResponse resp) throws IOException { 
        super(resp); 
        buffer = new ByteArrayOutputStream();// 真正存储数据的流 
        out = new WapperedOutputStream(buffer); 
        writer = new PrintWriter(new OutputStreamWriter(buffer, this.getCharacterEncoding())); 
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return out;
    }
    @Override
    public PrintWriter getWriter() throws UnsupportedEncodingException {
        return writer;
    }
    @Override
    public void flushBuffer() throws IOException {
        if (out != null) {
            out.flush();
        }
        if (writer != null) {
            writer.flush();
        }
    }
    @Override
    public void reset() {
        buffer.reset();
    }

    public byte[] getResponseData() throws IOException {
        flushBuffer();
        return buffer.toByteArray();
    }

    private class WapperedOutputStream extends ServletOutputStream {
        private ByteArrayOutputStream bos = null;
        public WapperedOutputStream(ByteArrayOutputStream stream) throws IOException {
            bos = stream;
        }

        @Override
        public void write(int b) throws IOException {
            bos.write(b);
        }
        @Override
        public void write(byte[] b) throws IOException {
            bos.write(b, 0, b.length);
        }
        @Override
        public boolean isReady() {
            return false;
        }
        @Override
        public void setWriteListener(WriteListener listener) {
        }
    }
}

2)过滤器:

public class MyFilter2 implements Filter {
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter init...");
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        MyResponseWrapper wrapResponse = new MyResponseWrapper((HttpServletResponse)response); 

        chain.doFilter(request, wrapResponse); 
        
        byte[] data = wrapResponse.getResponseData(); 
        System.out.println("原始数据: " + new String(data)); 
        String tempData = new String("jack"); 
        ServletOutputStream out = response.getOutputStream(); 
        out.write(tempData.getBytes()); 
        out.flush(); 

    }
    
    @Override
    public void destroy() {
        System.out.println("filter destory...");
    }
}

 

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
过滤器是Spring Boot中的一个重要组件,可以在HTTP请求被处理之前或响应被发送回客户端之前对请求和响应进行拦截和处理,从而实现一些功能,比如IP过滤。 下面是一个简单的IP过滤器实现: 1. 创建一个名为IpFilter的类,实现javax.servlet.Filter接口: ``` @Component public class IpFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; String ipAddress = req.getRemoteAddr(); // 实现IP过滤逻辑 if (!"127.0.0.1".equals(ipAddress)) { HttpServletResponse res = (HttpServletResponse) response; res.sendError(HttpServletResponse.SC_UNAUTHORIZED, "IP地址不允许访问!"); return; } chain.doFilter(request, response); } } ``` 2. 在Spring Boot应用入口类中添加注解@EnableWebMvc和@ComponentScan,以启用Spring MVC和扫描过滤器类: ``` @EnableWebMvc @ComponentScan(basePackages = "com.example.filter") @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 3. 在Spring Boot应用的配置文件中,添加过滤器的配置: ``` @Bean public FilterRegistrationBean<Filter> ipFilter() { FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>(); registration.setFilter(new IpFilter()); registration.addUrlPatterns("/*"); registration.setName("ipFilter"); registration.setOrder(1); return registration; } ``` 在以上代码中,我们创建了一个FilterRegistrationBean类型的Bean,使用setFilter方法设置要使用的过滤器类,使用addUrlPatterns方法设置要拦截的URL模式,使用setName方法设置过滤器名称,使用setOrder方法设置过滤器执行顺序。 通过以上步骤,我们就成功地实现了一个IP过滤器。需要注意的是,我们可以根据实际需求,修改过滤器的实现逻辑,比如可以从请求头中获取IP地址,或者通过配置文件设置允许访问的IP地址列表等。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赶路人儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值