Request的包装类HttpServletRequestWrapper的使⽤说明
⽬录
Request的包装类HttpServletRequestWrapper使⽤
⼤致的意思是:
上述⽅案解决了
HttpServletRequestWrapper和HttpServletResponseWrapper使⽤时的坑
WrapperRequest和WrapperResponse的使⽤
这⾥涉及到的坑
坑1
坑2
解决问题
问题延伸
Request的包装类HttpServletRequestWrapper使⽤
在使⽤zuul进⾏鉴权的时候,我们希望从请求Request中获取输⼊流,解析⾥⾯的内容,奈何InputStream只能被读取⼀次。为啥呢?源码⾥是这样说的:public int read(byte[] b,int off, int len)
   Reads up to len bytes of data into an array of bytes from this input stream. Ifpos equals count, then -1 is returned to indicate end of file.
Otherwise, the number k of bytes read is equal to the smaller of len and count-pos.If k is positive, then bytes buf[pos] through buf[pos+k-1] are copied into b[off] through b[off+k-1] in the manner performed by System.arraycopy. The value k is added into pos and k is returned. 
⼤致的意思是:
在InputStream读取的时候,会有⼀个pos指针,它指⽰每次读取之后下⼀次要读取的起始位置。在每次读取后会更新pos的值,当你下次再来读取的时候是从pos的位置开始的,⽽不是从头开始,所以第⼆次获取String中的值的时候是不全的,API中提供了⼀个解决办法:reset()。但我发现在inputStream和servlet中根本不起作⽤。提⽰ mark/reset not supported 。意思是只有重写过markSupported()⽅法的IO
流才可以⽤。所以⼀般我们使⽤inputStream,最好在⼀次内处理完所有逻辑。
那么就没法在中途获取请求流中的数据么?当然有办法了,我可是PPZ,只需要重写Request缓存⼀下流中的数据就好了,实现代码如下:BodyReaderHttpServletRequestWrapper.java
authUtils;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;jsessionid
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class BodyReaderHttpServletRequestWrapper extends
HttpServletRequestWrapper {
// private final byte[] body;
-----》private byte[] body;《-------
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
System.out.println("-------------------打印请求的头信息------------------------------");
Enumeration<?> e = HeaderNames()  ;
while(e.hasMoreElements()){
String name = (String) e.nextElement();
String value = Header(name);
// System.out.println(name+" = "+value);
}
-----》获取流中的数据缓存到字节数组中,以后要读数据就⽤这⾥的《------
body = BodyString(request).getBytes(Charset.forName("UTF-8"));
}
/**
* 从请求的头部获取⽤户的⾝份识别id;
* @param request
* @return
*/
public String getJsessionidFromHeader(HttpServletRequest request) {
String jsessionid = null;//识别⽤户⾝份的id;
Enumeration<?> e = HeaderNames()  ;
while(e.hasMoreElements()){
String name = (String) e.nextElement();
String value = Header(name);
//cookie = JSESSIONID=B926F6024438D4C693A5E5881595160C; SESSION=458e80dc-e354-4af3-a501-74504a873e70
if("cookie".equals(name)) {
jsessionid = value.split(";")[0].split("=")[1];
}
System.out.println(name+"="+value);
}
// System.out.println("======jsessionid========>"+jsessionid);
return jsessionid;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
------》从缓存的数据中读取数据《------
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
public int read() throws IOException {
ad();
}
@Override
public boolean isFinished() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setReadListener(ReadListener listener) {
// TODO Auto-generated method stub
}
};
}
@Override
public String getHeader(String name) {
Header(name);
}
@Override
public Enumeration<String> getHeaderNames() {
HeaderNames();
}
/* @Override
public Enumeration<String> getHeaders(String name) {
Headers(name);
}  */
/**
* content-type=text/plain;charset=UTF-8
* 重写getHeaders⽅法,实现⾃定义Content-Type;
*/
@Override
public Enumeration<String> getHeaders(String name) {
if ((null != name && name.equals("Content-Type"))||(null != name && name.equals("content-type"))) {
return new Enumeration<String>() {
private boolean hasGetted = false;
@Override
public String nextElement() {
if (hasGetted) {
throw new NoSuchElementException();
} else {
hasGetted = true;
return "application/json;charset=utf-8";
}
}
@Override
public boolean hasMoreElements() {
return !hasGetted;
}
};
}
Headers(name);
}
/**
* 添加⾃定义信息到请求体;
* @param customMsg:⾃定义的添加到请求体中的信息;
*/
public void appendCustomMsgToReqBody(String customMsg) {
String oldBodyString = BodyString(this);//oldBodyString⼀定是通过当前对象的输⼊流解析得来的,否则接收时会报EOFException;
String appendMsg = HttpHelper.appendCustomMsgToReqBody(customMsg);
String requestBodyAfterAppend = appendMsg + "," +oldBodyString;
//this.body = HttpHelper.appendCustomMsgToReqBody(HttpHelper.appendCustomMsgToReqBody(customMsg)+(BodyString(this))).getBytes(Charset.forName("UTF-8"));        //this.body = HttpHelper.appendCustomMsgToReqBody((BodyString(this))).getBytes(Charset.forName("UTF-8"));
this.body = HttpHelper.appendCustomMsgToReqBody(requestBodyAfterAppend).getBytes(Charset.forName("UTF-8"));
}
}
HttpHelper.java
authUtils;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import javax.servlet.ServletRequest;
public class HttpHelper {
/
**
* 获取post请求中的Body
*
* @param request
*/
public static String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = InputStream();
/
/读取流并将流写出去,避免数据流中断;
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = adLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
String();
}
//添加⾃定义的信息到请求体中;
public static String appendCustomMsgToReqBody(String newReqBodyStr) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
String newReqBody = null;
try {
/
/通过字符串构造输⼊流;
inputStream = String2InputStream(newReqBodyStr);
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = adLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//返回字符串;
newReqBody = sb.toString();
return newReqBody;
}
//将字符串转化为输⼊流;
public static InputStream String2InputStream(String str) {
ByteArrayInputStream stream = null;
try {
stream = new Bytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return stream;
}
}
上述⽅案解决了
使⽤InpuStream()⽅法读取流中的数据只能读取⼀次的问题,其实当我们在使⽤第三⽅接⼝时,如果请求头信息和我们的服务所需不⼀致,例如第三⽅接⼝中头部信息为:content-type=text/plain;charset=UTF-8
⽽我们需要的是:”application/json;charset=utf-8”时,我们也是可以通过重写对应的⽅法对请求的头部
信息进⾏修改的,代码如下:
/**
* content-type=text/plain;charset=UTF-8
* 重写getHeaders⽅法,实现⾃定义Content-Type;
*/
public Enumeration<String> getHeaders(String name) {
if ((null != name && name.equals("Content-Type"))||(null != name && name.equals("content-type"))) {
return new Enumeration<String>() {
private boolean hasGetted = false;
@Override
public String nextElement() {
if (hasGetted) {
throw new NoSuchElementException();
} else {
hasGetted = true;
return "application/json;charset=utf-8";
}
}
@Override
public boolean hasMoreElements() {
return !hasGetted;
}
};
}
Headers(name);
}
当我们在后端设置了头部信息后,如果不出意外,前端发送的请求将变为简单请求,这样,服务器的处理机制将简单很多。
HttpServletRequestWrapper和HttpServletResponseWrapper使⽤时的坑
WrapperRequest和WrapperResponse的使⽤
在做JavaWeb开发过程中如果想拿到请求参数和返回数据的话我们就会使⽤到这两个类,从类名上就可以看出是包装类,通过这两个类的包装我们可以使⽤移花接⽊的⽅式获取到对应的参数数据。
这⾥涉及到的坑
坑1
如果请求参数在Body内时取出参数后,后端程序就⽆法再次取出数据
这个和InputStream不能重复读有关,这⾥需要将Request中的数据⾃⼰保存⼀份然后在使⽤的时候给出新的InputStream,这样就可以避免使⽤同⼀个InputStream读取完数据后⽆法重新读取数据
@Override
public ServletInputStream getInputStream() throws IOException {
//这⾥每次都重新创建了⼀个InputStream
final ByteArrayInputStream inputStream = new ByteArrayInputStream(bodyData);
return new ServletInputStream() {
@Override
public int read() throws IOException {
ad();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
坑2
使⽤HttpServletResponseWrapper包装Response后⽆法返回数据或者⽆法返回html,css等数据
这个跟⽹上的教程有关,⼤多⽹上的教程是这样的如下代码:
//先定义⼀个Filter类包装对应的request和response
public class WrapperFilter extends OncePerRequestFilter {
private static Logger logger = Logger(WrapperFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.debug(" request mapping {} {}", RequestURL(), RequestURI());
RequestWrapper requestWrapper = new RequestWrapper(request);
ResponseWrapper responseWrapper = new ResponseWrapper(response);
filterChain.doFilter(requestWrapper, responseWrapper);
}
//response的包装类
public class ResponseWrapper extends HttpServletResponseWrapper {
private static Logger logger = Logger(ResponseWrapper.class);
private final ByteArrayOutputStream buffer;
private final ServletOutputStream out;
private final PrintWriter writer;
public ResponseWrapper(HttpServletResponse response) throws IOException {
super(response);
buffer = new ByteArrayOutputStream(2048);
out = new WrapperOutputStream(buffer);
writer = new PrintWriter(buffer);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return out;
}
/**
*    当获取字符输出流时,实际获取的是我们⾃⼰包装的字符输出流
*/
@Override
public PrintWriter getWriter() {
return writer;
}
/**
*    获取返回的数据内容,这个是截获的数据
*/
public byte[] getResponseData() throws IOException {
flushBuffer();
ByteArray();
}
public String getContent() throws IOException {
flushBuffer();
String();
}
}
上⾯的代码虽然是可以获取到数据的但是,数据就⽆法返回到前端页⾯了,那么为什么会出现这样的问题呢,咱们来分析⼀下。
1、包装类对response进⾏了包装,并且重写了getWriter和getOutputStream 这样就可以保证后端使⽤response向前端写数据时写到我们定义的buffer中
2、这个包装类是不范围的,也就是只在WrapperFilter 之后有效,但是浏览器从response读取数据明显是在WrapperFilter的范围之外的
也就是说浏览器从reponse读取数据时⽆法使⽤ResponseWrapper⽽只能使⽤response 这个默认是ResponseFacade
3、那么问题来了咱们上⾯有往response中写⼊数据吗,显然是没有的也就是写数据只在ResponseWrapper中有⽽ResponseFacade 是没有数据的所以浏览器了就⽆法读取到返回的数据啦。
清楚以上问题后问题就变得简单得多了,那么我们只需要往ResponseFacade 中也定⼊⼀份数据就可以了
解决问题
Filter的内容不变
ResponseWrapper中的代码如下修改
public class ResponseWrapper extends HttpServletResponseWrapper {
private static Logger logger = Logger(ResponseWrapper.class);
private final ByteArrayOutputStream buffer;
private final ServletOutputStream out;
private final PrintWriter writer;
public ResponseWrapper(HttpServletResponse response) throws IOException {
super(response);
buffer = new ByteArrayOutputStream(2048);
//这⾥将response也传⼊了WrapperOutputStream 和 WrapperWriter
out = new WrapperOutputStream(buffer,  response);
writer = new WrapperWriter(buffer, response);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return out;
}
/**
*    当获取字符输出流时,实际获取的是我们⾃⼰包装的字符输出流
*/
@Override
public PrintWriter getWriter() {
return writer;
}
public byte[] getResponseData() throws IOException {
flushBuffer();
ByteArray();
}
public String getContent() throws IOException {
flushBuffer();
String();
}
}
这⾥将response也传⼊了WrapperOutputStream 和 WrapperWriter 这样我们在做数据写⼊的时候就可以同时向reponse中写⼊数据了
这两个类的实现如下:
public class WrapperOutputStream extends ServletOutputStream {
private OutputStream innerOut;
private HttpServletResponse response;
public WrapperOutputStream(OutputStream innerOut, HttpServletResponse response) {
super();
this.innerOut = innerOut;