[英]Get the POST request body from HttpServletRequest
我正在嘗試從 HttpServletRequest object 獲取整個主體。
我正在關注的代碼如下所示:
if ( request.getMethod().equals("POST") )
{
StringBuffer sb = new StringBuffer();
BufferedReader bufferedReader = null;
String content = "";
try {
//InputStream inputStream = request.getInputStream();
//inputStream.available();
//if (inputStream != null) {
bufferedReader = request.getReader() ; //new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead;
while ( (bytesRead = bufferedReader.read(charBuffer)) != -1 ) {
sb.append(charBuffer, 0, bytesRead);
}
//} else {
// sb.append("");
//}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
test = sb.toString();
}
我正在使用 curl 和 wget 測試功能,如下所示:
curl --header "MD5: abcd" -F "fileupload=@filename.txt http://localhost:8080/abcd.html"
wget --header="MD5: abcd" --post-data='{"imei":"351553012623446","hni":"310150","wdp":false}' http://localhost:8080/abcd.html"
但是while ( (bytesRead = bufferedReader.read(charBuffer)) != -1 )
沒有返回任何東西,所以我沒有在 StringBuffer 上附加任何東西。
在 Java 8 中,你可以用更簡單和干凈的方式來完成:
if ("POST".equalsIgnoreCase(request.getMethod()))
{
test = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
}
使用 commons-io 的簡單方法。
IOUtils.toString(request.getReader());
https://commons.apache.org/proper/commons-io/javadocs/api-2.5/org/apache/commons/io/IOUtils.html
請注意,您的代碼非常嘈雜。 我知道這個線程很舊,但無論如何還是有很多人會閱讀它。 你可以使用guava庫做同樣的事情:
if ("POST".equalsIgnoreCase(request.getMethod())) {
test = CharStreams.toString(request.getReader());
}
如果你想要的只是 POST 請求正文,你可以使用這樣的方法:
static String extractPostRequestBody(HttpServletRequest request) throws IOException {
if ("POST".equalsIgnoreCase(request.getMethod())) {
Scanner s = new Scanner(request.getInputStream(), "UTF-8").useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
return "";
}
這適用於 GET 和 POST:
@Context
private HttpServletRequest httpRequest;
private void printRequest(HttpServletRequest httpRequest) {
System.out.println(" \n\n Headers");
Enumeration headerNames = httpRequest.getHeaderNames();
while(headerNames.hasMoreElements()) {
String headerName = (String)headerNames.nextElement();
System.out.println(headerName + " = " + httpRequest.getHeader(headerName));
}
System.out.println("\n\nParameters");
Enumeration params = httpRequest.getParameterNames();
while(params.hasMoreElements()){
String paramName = (String)params.nextElement();
System.out.println(paramName + " = " + httpRequest.getParameter(paramName));
}
System.out.println("\n\n Row data");
System.out.println(extractPostRequestBody(httpRequest));
}
static String extractPostRequestBody(HttpServletRequest request) {
if ("POST".equalsIgnoreCase(request.getMethod())) {
Scanner s = null;
try {
s = new Scanner(request.getInputStream(), "UTF-8").useDelimiter("\\A");
} catch (IOException e) {
e.printStackTrace();
}
return s.hasNext() ? s.next() : "";
}
return "";
}
如果請求體是空的,那么它只是意味着它已經被預先消耗了。 例如,通過request.getParameter()
、 getParameterValues()
或getParameterMap()
調用。 只需從代碼中刪除執行這些調用的行。
request.getReader().lines().reduce("",String::concat)
tim
和密碼12345
。 上面代碼的 output 看起來像這樣:{ "username":"tim", "password": "12345"}
這將適用於所有 HTTP 方法。
public class HttpRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public HttpRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = IOUtils.toString(request.getReader());
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(getBody().getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
return servletInputStream;
}
public String getBody() {
return this.body;
}
}
我就是這樣解決了那個情況的。 我創建了一個 util 方法,該方法返回從請求正文中提取的對象,使用能夠接收讀取器的 ObjectMapper 的 readValue 方法。
public static <T> T getBody(ResourceRequest request, Class<T> class) {
T objectFromBody = null;
try {
ObjectMapper objectMapper = new ObjectMapper();
HttpServletRequest httpServletRequest = PortalUtil.getHttpServletRequest(request);
objectFromBody = objectMapper.readValue(httpServletRequest.getReader(), class);
} catch (IOException ex) {
log.error("Error message", ex);
}
return objectFromBody;
}
我個人使用此代碼(在開發服務器上,而不是在生產中)。 似乎工作。 主要的難點在於,一旦你讀取了請求正文,它就會丟失並且不會傳輸到應用程序中。 所以你必須先“緩存”它。
/* Export this filter as a jar and place it under directory ".../tomcat/lib" on your Tomcat server/
In the lib directory, also place the dependencies you need
(ex. org.apache.commons.io => commons-io-2.8.0.jar)
Once this is done, in order to activate the filter, on the Tomcat server:
o in .../tomcat/conf/server.xml, add:
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" [%{postdata}r] %s %b"/>
=> the server will log the "postdata" attribute we generate in the Java code.
o in .../tomcat/conf/web.xml, add:
<filter>
<filter-name>post-data-dumper-filter</filter-name>
<filter-class>filters.PostDataDumperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>post-data-dumper-filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Once you've done this, restart your tomcat server. You will get extra infos in file "localhost_access_log.<date>.txt"
*/
package filters;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.stream.Collectors;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils;
class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedBytes;
public MultiReadHttpServletRequest(HttpServletRequest request) {
super(request);
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (cachedBytes == null)
cacheInputStream();
return new CachedServletInputStream();
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
private void cacheInputStream() throws IOException {
/* Cache the inputstream in order to read it multiple times.
*/
cachedBytes = new ByteArrayOutputStream();
IOUtils.copy(super.getInputStream(), cachedBytes);
}
/* An input stream which reads the cached request body */
public class CachedServletInputStream extends ServletInputStream {
private ByteArrayInputStream input;
public CachedServletInputStream() {
/* create a new input stream from the cached request body */
input = new ByteArrayInputStream(cachedBytes.toByteArray());
}
//---------------------
@Override
public int read() throws IOException {
return input.read();
}
@Override
public boolean isFinished() {
return input.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
//---------------------
@Override
public void setReadListener(ReadListener arg0) {
// TODO Auto-generated method stub
// Ex. : throw new RuntimeException("Not implemented");
}
}
}
public final class PostDataDumperFilter implements Filter {
private FilterConfig filterConfig = null;
public void destroy() {
this.filterConfig = null;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (filterConfig == null)
return;
StringBuffer output = new StringBuffer();
output.append("PostDataDumperFilter-");
/* Wrap the request in order to be able to read its body multiple times */
MultiReadHttpServletRequest multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) request);
// TODO : test the method in order not to log the body when receiving GET/DELETE requests ?
// I finally leave it "as it", since I've seen GET requests containing bodies (hell...).
output.append("Content-type=" + multiReadRequest.getContentType());
output.append(" - HTTP Method=" + multiReadRequest.getMethod());
output.append(" - REQUEST BODY = " + multiReadRequest.getReader().lines().collect(Collectors.joining(System.lineSeparator())));
// Log the request parameters:
Enumeration names = multiReadRequest.getParameterNames();
if (names.hasMoreElements()) {
output.append("- REQUEST PARAMS = ");
}
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
output.append(name + "=");
String values[] = multiReadRequest.getParameterValues(name);
for (int i = 0; i < values.length; i++) {
if (i > 0) output.append("' ");
output.append(values[i]);
}
if (names.hasMoreElements()) output.append("&");
}
multiReadRequest.setAttribute("postdata", output);
chain.doFilter(multiReadRequest, response);
}
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
}
實施其他解決方案可能會導致您出現以下異常
java.lang.IllegalStateException: getInputStream() has already been called for this request
要讀取 HttpServletRequest,需要執行以下操作。
背景:
為了從請求中獲取請求主體,提供了 HttpServletRequest 和 InputStream class。getReader() 通常用於 stream 請求。 這個function內部調用了getInputStream() function,返回給我們stream供我們讀取請求。 現在,請注意它是一個 stream,並且只能打開一次。 因此,在閱讀本文時(即實施此線程中給出的解決方案),它通常會拋出“流已關閉”。 例外。 現在發生這種情況是因為 tomcat 服務器已經打開並讀取了一次請求。 因此,我們需要在這里實現一個包裝器,它可以幫助我們通過保留一個實例來重新打開一個已經讀取過的 stream。 同樣,這個包裝器不能直接使用,而是需要在 spring 過濾器級別添加,而 tomcat 服務器正在讀取它。
代碼:
Servlet 請求包裝器 Class:
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
@Slf4j
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final String body;public MyHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
log.error("Error reading the request body...");
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
log.error("Error closing bufferedReader...");
}
}
}
body = stringBuilder.toString();
}
@Override
public ServletInputStream getInputStream () {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream inputStream = new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
public int read () throws IOException {
return byteArrayInputStream.read();
}
};
return inputStream;
}
@Override
public BufferedReader getReader(){
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
現在我們需要使用包裝器在過濾器中實現它,如下所示。
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;
@Component
@Order(1)
@Slf4j
public class ServletFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if (servletRequest instanceof HttpServletRequest) {
requestWrapper = new MyHttpServletRequestWrapper((HttpServletRequest) servletRequest);
}
if (Objects.isNull(requestWrapper)){
filterChain.doFilter(servletRequest, servletResponse);
} else {
filterChain.doFilter(requestWrapper, servletResponse);
}
}
}
然后,可以按如下方式使用建議的實現來獲取請求正文,如下所示:
private String getRequestBody(HttpServletRequest request) {
try {
return request.getReader().lines().collect(Collectors.joining());
}catch (Exception e){
e.printStackTrace();
return "{}";
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.