Request的包裝類HttpServletRequestWrapper的使用說明
Request的包裝類HttpServletRequestWrapper使用
在使用zuul進(jìn)行鑒權(quán)的時(shí)候,我們希望從請(qǐng)求Request中獲取輸入流,解析里面的內(nèi)容,奈何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讀取的時(shí)候,會(huì)有一個(gè)pos指針,它指示每次讀取之后下一次要讀取的起始位置。在每次讀取后會(huì)更新pos的值,當(dāng)你下次再來(lái)讀取的時(shí)候是從pos的位置開始的,而不是從頭開始,所以第二次獲取String中的值的時(shí)候是不全的,API中提供了一個(gè)解決辦法:reset()。但我發(fā)現(xiàn)在inputStream和servlet中根本不起作用。提示 mark/reset not supported 。意思是只有重寫過markSupported()方法的IO流才可以用。所以一般我們使用inputStream,最好在一次內(nèi)處理完所有邏輯。
那么就沒法在中途獲取請(qǐng)求流中的數(shù)據(jù)么?當(dāng)然有辦法了,我可是PPZ,只需要重寫Request緩存一下流中的數(shù)據(jù)就好了,實(shí)現(xiàn)代碼如下:
BodyReaderHttpServletRequestWrapper.java
package com.neo.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;
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("-------------------打印請(qǐng)求的頭信息------------------------------");
Enumeration<?> e = request.getHeaderNames() ;
while(e.hasMoreElements()){
String name = (String) e.nextElement();
String value = request.getHeader(name);
// System.out.println(name+" = "+value);
}
-----》獲取流中的數(shù)據(jù)緩存到字節(jié)數(shù)組中,以后要讀數(shù)據(jù)就用這里的《------
body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
}
/**
* 從請(qǐng)求的頭部獲取用戶的身份識(shí)別id;
* @param request
* @return
*/
public String getJsessionidFromHeader(HttpServletRequest request) {
String jsessionid = null;//識(shí)別用戶身份的id;
Enumeration<?> e = request.getHeaderNames() ;
while(e.hasMoreElements()){
String name = (String) e.nextElement();
String value = request.getHeader(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 {
------》從緩存的數(shù)據(jù)中讀取數(shù)據(jù)《------
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
public int read() throws IOException {
return bais.read();
}
@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) {
return super.getHeader(name);
}
@Override
public Enumeration<String> getHeaderNames() {
return super.getHeaderNames();
}
/* @Override
public Enumeration<String> getHeaders(String name) {
return super.getHeaders(name);
} */
/**
* content-type=text/plain;charset=UTF-8
* 重寫getHeaders方法,實(shí)現(xiàn)自定義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;
}
};
}
return super.getHeaders(name);
}
/**
* 添加自定義信息到請(qǐng)求體;
* @param customMsg:自定義的添加到請(qǐng)求體中的信息;
*/
public void appendCustomMsgToReqBody(String customMsg) {
String oldBodyString = HttpHelper.getBodyString(this);//oldBodyString一定是通過當(dāng)前對(duì)象的輸入流解析得來(lái)的,否則接收時(shí)會(huì)報(bào)EOFException;
String appendMsg = HttpHelper.appendCustomMsgToReqBody(customMsg);
String requestBodyAfterAppend = appendMsg + "," +oldBodyString;
//this.body = HttpHelper.appendCustomMsgToReqBody(HttpHelper.appendCustomMsgToReqBody(customMsg)+(HttpHelper.getBodyString(this))).getBytes(Charset.forName("UTF-8"));
//this.body = HttpHelper.appendCustomMsgToReqBody((HttpHelper.getBodyString(this))).getBytes(Charset.forName("UTF-8"));
this.body = HttpHelper.appendCustomMsgToReqBody(requestBodyAfterAppend).getBytes(Charset.forName("UTF-8"));
}
}
HttpHelper.java
package com.neo.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請(qǐng)求中的Body
*
* @param request
* @return
*/
public static String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
//讀取流并將流寫出去,避免數(shù)據(jù)流中斷;
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != 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();
}
}
}
return sb.toString();
}
//添加自定義的信息到請(qǐng)求體中;
public static String appendCustomMsgToReqBody(String newReqBodyStr) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
String newReqBody = null;
try {
//通過字符串構(gòu)造輸入流;
inputStream = String2InputStream(newReqBodyStr);
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != 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;
}
//將字符串轉(zhuǎn)化為輸入流;
public static InputStream String2InputStream(String str) {
ByteArrayInputStream stream = null;
try {
stream = new ByteArrayInputStream(str.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return stream;
}
}
上述方案解決了
使用request.getInpuStream()方法讀取流中的數(shù)據(jù)只能讀取一次的問題,其實(shí)當(dāng)我們?cè)谑褂玫谌浇涌跁r(shí),如果請(qǐng)求頭信息和我們的服務(wù)所需不一致,例如第三方接口中頭部信息為:content-type=text/plain;charset=UTF-8
而我們需要的是:”application/json;charset=utf-8”時(shí),我們也是可以通過重寫對(duì)應(yīng)的方法對(duì)請(qǐng)求的頭部信息進(jìn)行修改的,代碼如下:
/**
* content-type=text/plain;charset=UTF-8
* 重寫getHeaders方法,實(shí)現(xiàn)自定義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;
}
};
}
return super.getHeaders(name);
}
當(dāng)我們?cè)诤蠖嗽O(shè)置了頭部信息后,如果不出意外,前端發(fā)送的請(qǐng)求將變?yōu)楹?jiǎn)單請(qǐng)求,這樣,服務(wù)器的處理機(jī)制將簡(jiǎn)單很多。
HttpServletRequestWrapper和HttpServletResponseWrapper使用時(shí)的坑
WrapperRequest和WrapperResponse的使用
在做JavaWeb開發(fā)過程中如果想拿到請(qǐng)求參數(shù)和返回?cái)?shù)據(jù)的話我們就會(huì)使用到這兩個(gè)類,從類名上就可以看出是包裝類,通過這兩個(gè)類的包裝我們可以使用移花接木的方式獲取到對(duì)應(yīng)的參數(shù)數(shù)據(jù)。
這里涉及到的坑
坑1
如果請(qǐng)求參數(shù)在Body內(nèi)時(shí)取出參數(shù)后,后端程序就無(wú)法再次取出數(shù)據(jù)
這個(gè)和InputStream不能重復(fù)讀有關(guān) ,這里需要將Request中的數(shù)據(jù)自己保存一份然后在使用的時(shí)候給出新的InputStream,這樣就可以避免使用同一個(gè)InputStream讀取完數(shù)據(jù)后無(wú)法重新讀取數(shù)據(jù)
@Override
public ServletInputStream getInputStream() throws IOException {
//這里每次都重新創(chuàng)建了一個(gè)InputStream
final ByteArrayInputStream inputStream = new ByteArrayInputStream(bodyData);
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 readListener) {
}
};
}
坑2
使用HttpServletResponseWrapper包裝Response后無(wú)法返回?cái)?shù)據(jù)或者無(wú)法返回html,css等數(shù)據(jù)
這個(gè)跟網(wǎng)上的教程有關(guān),大多網(wǎng)上的教程是這樣的如下代碼:
//先定義一個(gè)Filter類包裝對(duì)應(yīng)的request和response
public class WrapperFilter extends OncePerRequestFilter {
private static Logger logger = LoggerFactory.getLogger(WrapperFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.debug(" request mapping {} {}", request.getRequestURL(), request.getRequestURI());
RequestWrapper requestWrapper = new RequestWrapper(request);
ResponseWrapper responseWrapper = new ResponseWrapper(response);
filterChain.doFilter(requestWrapper, responseWrapper);
}
//response的包裝類
public class ResponseWrapper extends HttpServletResponseWrapper {
private static Logger logger = LoggerFactory.getLogger(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;
}
/**
* 當(dāng)獲取字符輸出流時(shí),實(shí)際獲取的是我們自己包裝的字符輸出流
*/
@Override
public PrintWriter getWriter() {
return writer;
}
/**
* 獲取返回的數(shù)據(jù)內(nèi)容,這個(gè)是截獲的數(shù)據(jù)
*/
public byte[] getResponseData() throws IOException {
flushBuffer();
return buffer.toByteArray();
}
public String getContent() throws IOException {
flushBuffer();
return buffer.toString();
}
}
上面的代碼雖然是可以獲取到數(shù)據(jù)的但是,數(shù)據(jù)就無(wú)法返回到前端頁(yè)面了,那么為什么會(huì)出現(xiàn)這樣的問題呢,咱們來(lái)分析一下。
1、包裝類對(duì)response進(jìn)行了包裝,并且重寫了getWriter和getOutputStream 這樣就可以保證后端使用response向前端寫數(shù)據(jù)時(shí)寫到我們定義的buffer中
2、這個(gè)包裝類是不范圍的,也就是只在WrapperFilter 之后有效,但是瀏覽器從response讀取數(shù)據(jù)明顯是在WrapperFilter的范圍之外的
也就是說瀏覽器從reponse讀取數(shù)據(jù)時(shí)無(wú)法使用ResponseWrapper而只能使用response 這個(gè)默認(rèn)是ResponseFacade
3、那么問題來(lái)了咱們上面有往response中寫入數(shù)據(jù)嗎,顯然是沒有的也就是寫數(shù)據(jù)只在ResponseWrapper中有而ResponseFacade 是沒有數(shù)據(jù)的所以瀏覽器了就無(wú)法讀取到返回的數(shù)據(jù)啦。
清楚以上問題后問題就變得簡(jiǎn)單得多了,那么我們只需要往ResponseFacade 中也定入一份數(shù)據(jù)就可以了
解決問題
Filter的內(nèi)容不變
ResponseWrapper中的代碼如下修改
public class ResponseWrapper extends HttpServletResponseWrapper {
private static Logger logger = LoggerFactory.getLogger(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;
}
/**
* 當(dāng)獲取字符輸出流時(shí),實(shí)際獲取的是我們自己包裝的字符輸出流
*/
@Override
public PrintWriter getWriter() {
return writer;
}
public byte[] getResponseData() throws IOException {
flushBuffer();
return buffer.toByteArray();
}
public String getContent() throws IOException {
flushBuffer();
return buffer.toString();
}
}
這里將response也傳入了WrapperOutputStream 和 WrapperWriter 這樣我們?cè)谧鰯?shù)據(jù)寫入的時(shí)候就可以同時(shí)向reponse中寫入數(shù)據(jù)了
這兩個(gè)類的實(shí)現(xiàn)如下:
public class WrapperOutputStream extends ServletOutputStream {
private OutputStream innerOut;
private HttpServletResponse response;
public WrapperOutputStream(OutputStream innerOut, HttpServletResponse response) {
super();
this.response = response;
this.innerOut = innerOut;
}
@Override
public boolean isReady() {
if(response == null){
return false;
}
try {
return response.getOutputStream().isReady();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
@Override
public void setWriteListener(WriteListener listener) {
if(response != null){
try {
((CoyoteOutputStream)response.getOutputStream()).setWriteListener(listener);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//關(guān)鍵在這里
@Override
public void write(int b) throws IOException {
if(response != null){
response.getOutputStream().write(b);
}
innerOut.write(b);
}
}
//另一個(gè)類
public class WrapperWriter extends PrintWriter {
private HttpServletResponse response;
ByteArrayOutputStream output;
public WrapperWriter(ByteArrayOutputStream out, HttpServletResponse response) {
super(out);
this.response = response;
this.output = out;
}
//關(guān)鍵在這里
@Override
public void write(int b){
super.write(b);
try {
response.getWriter().write(b);
} catch (IOException e) {
e.printStackTrace();
this.setError();
}
}
//關(guān)鍵在這里
@Override
public void write(String s, int off, int len) {
super.write(s,off,len);
try {
response.getWriter().write(s,off,len);
} catch (IOException e) {
e.printStackTrace();
this.setError();
}
}
}
以上可以看到數(shù)據(jù)的寫入變成了寫兩份一份寫到了自定義的對(duì)象中一份寫到了response中這樣返回到前端的responnse就不會(huì)沒有數(shù)據(jù)了
到此問題完全解決,這里還需要注意的就是PrintWriter 有多個(gè)writer重載需要都進(jìn)行重寫才行
問題延伸
有人會(huì)問能不能直接將response中的OutputStream和Writer獲取到分配給對(duì)應(yīng)的WrapperOutputStream 和WrapperWriter而不是直接傳入response本身,答案是不可以的,response是不能同時(shí)獲取OutputStream和Writer的因?yàn)樗麄儾僮鞯氖峭粋€(gè)數(shù)據(jù),所以ResponseFacade 實(shí)現(xiàn)時(shí)對(duì)其進(jìn)行了保護(hù)——同時(shí)只能獲取OutputStream和Writer中的一個(gè)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
淺析 ArrayList 和 LinkedList 有什么區(qū)別
ArrayList 和 LinkedList 有什么區(qū)別,是面試官非常喜歡問的一個(gè)問題。今天通過本文給大家詳細(xì)介紹下,感興趣的朋友跟隨小編一起看看吧2020-10-10
Java servlet 使用 PrintWriter 時(shí)的編碼與亂碼的示例代碼
本篇文章主要介紹了Java servlet 使用 PrintWriter 時(shí)的編碼與亂碼的示例代碼,探討了 PrintWriter 的缺省編碼與普通字符流的缺省編碼的差異,具有一定的參考價(jià)值,有興趣的可以了解一下2017-11-11
IDEA之啟動(dòng)參數(shù),配置文件默認(rèn)參數(shù)的操作
這篇文章主要介紹了IDEA之啟動(dòng)參數(shù),配置文件默認(rèn)參數(shù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧2021-01-01

