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

╰半橙微兮° 2022-10-05 09:51 363阅读 0赞

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

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

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

一、修改请求

1、ServletRequestWrapper介绍:

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpdXhpYW83MjM4NDY_size_16_color_FFFFFF_t_70

简单画一下UML图:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpdXhpYW83MjM4NDY_size_16_color_FFFFFF_t_70 1

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

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

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

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xpdXhpYW83MjM4NDY_size_16_color_FFFFFF_t_70 2

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

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

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

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

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

1)定义MyRequestWrapper

  1. public class MyRequestWrapper extends HttpServletRequestWrapper {
  2. private Map<String,String[]> paramsMap;
  3. private Map<String, String> headerMap;
  4. public MyRequestWrapper(HttpServletRequest request) {
  5. super(request);
  6. this.paramsMap = new HashMap<>();
  7. this.paramsMap.putAll(request.getParameterMap());
  8. this.headerMap = new HashMap<>();
  9. }
  10. public void addParameter(String name,String value) {
  11. String[] parameterValues = getParameterValues(name);
  12. if (parameterValues == null) {
  13. this.paramsMap.put(name, new String[] {value});
  14. } else {
  15. parameterValues = Arrays.copyOf(parameterValues, parameterValues.length + 1);
  16. parameterValues[parameterValues.length-1] = value;
  17. this.paramsMap.put(name, parameterValues);
  18. }
  19. }
  20. @Override
  21. public Enumeration<String> getParameterNames() {
  22. Vector<String> vector = new Vector<String>(paramsMap.keySet());
  23. return vector.elements();
  24. }
  25. @Override
  26. public String getParameter(String name) {
  27. if(paramsMap.containsKey(name)) {
  28. return paramsMap.get(name).length > 0 ? paramsMap.get(name)[0] : null;
  29. }
  30. return null;
  31. }
  32. @Override
  33. public String[] getParameterValues(String name) {
  34. return paramsMap.get(name);
  35. }
  36. @Override
  37. public Map<String, String[]> getParameterMap() {
  38. return Collections.unmodifiableMap(paramsMap);
  39. }
  40. @Override
  41. public String getQueryString() {
  42. StringBuilder sb = new StringBuilder();
  43. if(paramsMap != null) {
  44. for(Map.Entry<String, String[]> en : paramsMap.entrySet()) {
  45. sb.append(en.getKey()).append("=").append(en.getValue()[0]).append("&");
  46. }
  47. }
  48. int len = sb.length();
  49. if(len > 0) {
  50. sb.deleteCharAt(len-1);
  51. }
  52. return sb.toString();
  53. }
  54. public void addHeader(String name, String value){
  55. this.headerMap.put(name, value);
  56. }
  57. @Override
  58. public String getHeader(String name) {
  59. String headerValue = headerMap.get(name);
  60. if (headerValue != null){
  61. return headerValue;
  62. }
  63. return super.getHeader(name);
  64. }
  65. @Override
  66. public Enumeration<String> getHeaderNames() {
  67. List<String> names = Collections.list(super.getHeaderNames());
  68. for (String name : headerMap.keySet()) {
  69. names.add(name);
  70. }
  71. return Collections.enumeration(names);
  72. }
  73. @Override
  74. public Enumeration<String> getHeaders(String name) {
  75. List<String> values = Collections.list(super.getHeaders(name));
  76. if (headerMap.containsKey(name)) {
  77. values.add(headerMap.get(name));
  78. }
  79. return Collections.enumeration(values);
  80. }
  81. }

2)Filter:

  1. public class MyFilter implements Filter {
  2. private FilterConfig filterConfig;
  3. @Override
  4. public void init(FilterConfig filterConfig) throws ServletException {
  5. this.filterConfig = filterConfig;
  6. System.out.println("filter init...");
  7. }
  8. @Override
  9. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  10. throws IOException, ServletException {
  11. MyRequestWrapper requestWrapper = new MyRequestWrapper((HttpServletRequest)request);
  12. modifyParameter(requestWrapper,request);
  13. modifyHeader(requestWrapper,request);
  14. chain.doFilter(requestWrapper, response);
  15. }
  16. //修改请求参数
  17. private void modifyParameter(MyRequestWrapper requestWrapper,ServletRequest request) {
  18. requestWrapper.addParameter("myParam", "test");
  19. }
  20. //修改请求头
  21. private void modifyHeader(MyRequestWrapper requestWrapper,ServletRequest request) {
  22. requestWrapper.addHeader("myHead", "test");
  23. }
  24. @Override
  25. public void destroy() {
  26. System.out.println("filter destory...");
  27. }
  28. }

3)配置Filter:

  1. <filter>
  2. <filter-name>myFilter</filter-name>
  3. <filter-class>cn.edu.nuc.springmvc_test.filter.MyFilter</filter-class>
  4. </filter>
  5. <filter-mapping>
  6. <filter-name>myFilter</filter-name>
  7. <url-pattern>/*</url-pattern>
  8. </filter-mapping>

4)测试:

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

  1. String header = request.getHeader("myHead");
  2. 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:

  1. import java.io.BufferedReader;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.ByteArrayOutputStream;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.io.InputStreamReader;
  7. import javax.servlet.ReadListener;
  8. import javax.servlet.ServletInputStream;
  9. import javax.servlet.http.HttpServletRequest;
  10. import javax.servlet.http.HttpServletRequestWrapper;
  11. public class MyRequestWrapper2 extends HttpServletRequestWrapper {
  12. private byte[] body;
  13. public MyRequestWrapper2(HttpServletRequest request) {
  14. super(request);
  15. try {
  16. body = read((InputStream)request.getInputStream());
  17. } catch (Exception e) {
  18. }
  19. }
  20. @Override
  21. public BufferedReader getReader() throws IOException {
  22. return new BufferedReader(new InputStreamReader(getInputStream()));
  23. }
  24. @Override
  25. public ServletInputStream getInputStream() throws IOException {
  26. final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
  27. return new ServletInputStream() {
  28. @Override
  29. public int read() throws IOException {
  30. return inputStream.read();
  31. }
  32. @Override
  33. public boolean isFinished() {
  34. return false;
  35. }
  36. @Override
  37. public boolean isReady() {
  38. return false;
  39. }
  40. @Override
  41. public void setReadListener(ReadListener listener) {
  42. }
  43. };
  44. }
  45. private byte[] read(InputStream inStream)throws Exception{
  46. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  47. byte[] buffer = new byte[1024];
  48. int len = 0;
  49. while((len=inStream.read(buffer))!=-1){
  50. outputStream.write(buffer, 0, len);
  51. }
  52. inStream.close();
  53. return outputStream.toByteArray();
  54. }
  55. }

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

B、Filter:

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

C、测试:

  1. private String inputStream2String(InputStream inputStream) {
  2. StringBuilder sb = new StringBuilder();
  3. BufferedReader reader = null;
  4. try {
  5. reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
  6. String line;
  7. while ((line = reader.readLine()) != null) {
  8. sb.append(line);
  9. }
  10. } catch (IOException e) {
  11. throw new RuntimeException(e);
  12. } finally {
  13. if (reader != null) {
  14. try {reader.close();} catch (IOException e) {}
  15. }
  16. }
  17. return sb.toString();
  18. }
  19. @RequestMapping(value="/test4")
  20. @ResponseBody
  21. public Map<String,String> test4(HttpServletRequest request,HttpServletResponse httpResponse) {
  22. Map<String ,String> res = new HashMap<>();
  23. //stream
  24. try {
  25. ServletInputStream inputStream = request.getInputStream();
  26. String inputStream2String = inputStream2String(inputStream);
  27. System.out.println(inputStream2String);
  28. ServletInputStream inputStream2 = request.getInputStream();
  29. String inputStream2String2 = inputStream2String(inputStream2);
  30. System.out.println(inputStream2String2);
  31. } catch (IOException e) {
  32. e.printStackTrace();
  33. }
  34. }

二、修改响应

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

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

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

示例:

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

  1. package cn.edu.nuc.springmvc_test.filter;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.IOException;
  4. import java.io.OutputStreamWriter;
  5. import java.io.PrintWriter;
  6. import java.io.UnsupportedEncodingException;
  7. import javax.servlet.ServletOutputStream;
  8. import javax.servlet.WriteListener;
  9. import javax.servlet.http.HttpServletResponse;
  10. import javax.servlet.http.HttpServletResponseWrapper;
  11. public class MyResponseWrapper extends HttpServletResponseWrapper {
  12. private ByteArrayOutputStream buffer = null;
  13. private ServletOutputStream out = null;
  14. private PrintWriter writer = null;
  15. public MyResponseWrapper(HttpServletResponse resp) throws IOException {
  16. super(resp);
  17. buffer = new ByteArrayOutputStream();// 真正存储数据的流
  18. out = new WapperedOutputStream(buffer);
  19. writer = new PrintWriter(new OutputStreamWriter(buffer, this.getCharacterEncoding()));
  20. }
  21. @Override
  22. public ServletOutputStream getOutputStream() throws IOException {
  23. return out;
  24. }
  25. @Override
  26. public PrintWriter getWriter() throws UnsupportedEncodingException {
  27. return writer;
  28. }
  29. @Override
  30. public void flushBuffer() throws IOException {
  31. if (out != null) {
  32. out.flush();
  33. }
  34. if (writer != null) {
  35. writer.flush();
  36. }
  37. }
  38. @Override
  39. public void reset() {
  40. buffer.reset();
  41. }
  42. public byte[] getResponseData() throws IOException {
  43. flushBuffer();
  44. return buffer.toByteArray();
  45. }
  46. private class WapperedOutputStream extends ServletOutputStream {
  47. private ByteArrayOutputStream bos = null;
  48. public WapperedOutputStream(ByteArrayOutputStream stream) throws IOException {
  49. bos = stream;
  50. }
  51. @Override
  52. public void write(int b) throws IOException {
  53. bos.write(b);
  54. }
  55. @Override
  56. public void write(byte[] b) throws IOException {
  57. bos.write(b, 0, b.length);
  58. }
  59. @Override
  60. public boolean isReady() {
  61. return false;
  62. }
  63. @Override
  64. public void setWriteListener(WriteListener listener) {
  65. }
  66. }
  67. }

2)过滤器:

  1. public class MyFilter2 implements Filter {
  2. @Override
  3. public void init(FilterConfig filterConfig) throws ServletException {
  4. System.out.println("filter init...");
  5. }
  6. @Override
  7. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  8. throws IOException, ServletException {
  9. MyResponseWrapper wrapResponse = new MyResponseWrapper((HttpServletResponse)response);
  10. chain.doFilter(request, wrapResponse);
  11. byte[] data = wrapResponse.getResponseData();
  12. System.out.println("原始数据: " + new String(data));
  13. String tempData = new String("jack");
  14. ServletOutputStream out = response.getOutputStream();
  15. out.write(tempData.getBytes());
  16. out.flush();
  17. }
  18. @Override
  19. public void destroy() {
  20. System.out.println("filter destory...");
  21. }
  22. }

发表评论

表情:
评论列表 (有 0 条评论,363人围观)

还没有评论,来说两句吧...

相关阅读