獲取 HttpServletRequest 中的請求體
HttpServletRequest#getInputStream() 獲取到請求的輸入流,從該輸入流中可以讀取到請求體。不過這個流在被我們的代碼 read 過後,之後的代碼就會報錯,因爲流已經被我們讀取過了 , 嘗試使用 mark() , reset() 也是不行的,會拋出異常。可以通過將 HttpServletRequest 對象包裝一層的方式來實現這個功能。
package org.hepeng.commons.http;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.commons.io.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
*
* @author he peng
* @date 2018/9/11
*/
public class BodyCachingHttpServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] body;
private ServletInputStreamWrapper inputStreamWrapper;
public BodyCachingHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.body = IOUtils.toByteArray(request.getInputStream());
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.body);
this.inputStreamWrapper = new ServletInputStreamWrapper(byteArrayInputStream);
resetInputStream();
}
private void resetInputStream() {
this.inputStreamWrapper.setInputStream(new ByteArrayInputStream(this.body != null ? this.body : new byte[0]));
}
public byte[] getBody() {
return body;
}
@Override
public ServletInputStream getInputStream() throws IOException {
return this.inputStreamWrapper;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.inputStreamWrapper));
}
@Data
@AllArgsConstructor
private static class ServletInputStreamWrapper extends ServletInputStream {
private InputStream inputStream;
@Override
public boolean isFinished() {
return true;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
@Override
public int read() throws IOException {
return this.inputStream.read();
}
}
}
獲取 HttpServletResponse 中的響應體
通過使用 ByteArrayOutputStream 將原 HttpSevletResponse 進行一層包裝就可以實現。ByteArrayOutputStream 是將數據寫入到它內部的緩衝區中,這樣我們就可以獲取到這個數據了。
package org.hepeng.commons.http;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
/**
* @author he peng
* @date 2018/10/1
*/
public class BodyCachingHttpServletResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
private HttpServletResponse response;
public BodyCachingHttpServletResponseWrapper(HttpServletResponse response) {
super(response);
this.response = response;
}
public byte[] getBody() {
return byteArrayOutputStream.toByteArray();
}
@Override
public ServletOutputStream getOutputStream() {
return new ServletOutputStreamWrapper(this.byteArrayOutputStream , this.response);
}
@Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(new OutputStreamWriter(this.byteArrayOutputStream , this.response.getCharacterEncoding()));
}
@Data
@AllArgsConstructor
private static class ServletOutputStreamWrapper extends ServletOutputStream {
private ByteArrayOutputStream outputStream;
private HttpServletResponse response;
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener listener) {
}
@Override
public void write(int b) throws IOException {
this.outputStream.write(b);
}
@Override
public void flush() throws IOException {
if (! this.response.isCommitted()) {
byte[] body = this.outputStream.toByteArray();
ServletOutputStream outputStream = this.response.getOutputStream();
outputStream.write(body);
outputStream.flush();
}
}
}
}
flush() 函數是必須提供的 ,否則流中的數據無法響應到客戶端 , ByteArrayOutputStream 沒有實現 flush() 。像 SpringMVC 這類框架會去調用這個響應輸出流中的 flush() 函數 ,而且有可能在出現多次調用的情況,多次調用會產生問題使得客戶端得到錯誤的數據,比如這樣的 :
{"errorCode":30001,"errorMsg":"用戶未認證","token":null,"entity":null}{"errorCode":30001,"errorMsg":"用戶未認證","token":null,"entity":null} ,出現這種情況就說明 flush() 被調用了兩次。所以需要在這裏判斷一下 HttpServletResponse#isCommitted() 。
獲取請求體、相應體的包裝類在 Filter 中的使用
package org.hepeng.commons.http.filter;
import com.tepin.commons.http.BodyCachingHttpServletRequestWrapper;
import com.tepin.commons.http.BodyCachingHttpServletResponseWrapper;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author he peng
* @date 2018/10/2
*/
public class DemoFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
BodyCachingHttpServletRequestWrapper requestWrapper =
new BodyCachingHttpServletRequestWrapper((HttpServletRequest) request);
byte[] requestBody = requestWrapper.getBody();
// TODO do something
BodyCachingHttpServletResponseWrapper responseWrapper =
new BodyCachingHttpServletResponseWrapper((HttpServletResponse) response);
chain.doFilter(requestWrapper , responseWrapper);
byte[] responseBody = responseWrapper.getBody();
// TODO do something
}
@Override
public void destroy() {
}
}