通过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
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
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都需要重写那些方法呢?
- 以流的方式获取输出——重写getOutputStream() //返回一般为打印流,其底层是对ServletOutputStream引用。
- 以字符方式获取输出——重写getWriter() //直接对ServletOutputStream的引用
- 刷新流——重写flushBuffer()
- 重置流——重写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...");
}
}
还没有评论,来说两句吧...