在使用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
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("-------------------打印请求的头信息------------------------------");
Enumeration<?> e = request.getHeaderNames() ;
while(e.hasMoreElements()){
String name = (String) e.nextElement();
String value = request.getHeader(name);
// System.out.println(name+" = "+value);
}
-----》获取流中的数据缓存到字节数组中,以后要读数据就用这里的《------
body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
}
/**
* 从请求的头部获取用户的身份识别id;
* @param request
* @return
*/
public String getJsessionidFromHeader(HttpServletRequest request) {
String jsessionid = null;//识别用户身份的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 {
------》从缓存的数据中读取数据《------
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方法,实现自定义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);
}
/**
* 添加自定义信息到请求体;
* @param customMsg:自定义的添加到请求体中的信息;
*/
public void appendCustomMsgToReqBody(String customMsg) {
String oldBodyString = HttpHelper.getBodyString(this);//oldBodyString一定是通过当前对象的输入流解析得来的,否则接收时会报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请求中的Body
*
* @param request
* @return
*/
public static String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
//读取流并将流写出去,避免数据流中断;
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();
}
//添加自定义的信息到请求体中;
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 = 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;
}
//将字符串转化为输入流;
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()方法读取流中的数据只能读取一次的问题,其实当我们在使用第三方接口时,如果请求头信息和我们的服务所需不一致,例如第三方接口中头部信息为:content-type=text/plain;charset=UTF-8
而我们需要的是:”application/json;charset=utf-8”时,我们也是可以通过重写对应的方法对请求的头部信息进行修改的,代码如下:
/**
* 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;
}
};
}
return super.getHeaders(name);
}
当我们在后端设置了头部信息后,如果不出意外,前端发送的请求将变为简单请求,这样,服务器的处理机制将简单很多。