从 HttpServletRequest 获取 POST 请求正文
Posted
技术标签:
【中文标题】从 HttpServletRequest 获取 POST 请求正文【英文标题】:Get the POST request body from HttpServletRequest 【发布时间】:2011-12-27 09:20:41 【问题描述】:我正在尝试从 HttpServletRequest 对象中获取整个主体。
我正在关注的代码如下所示:
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 上附加任何内容。
【问题讨论】:
【参考方案1】:我个人使用此代码(在开发服务器上,而不是在生产中)。似乎工作。主要的难点是,一旦你读取了请求体,它就会丢失并且不会传输到应用程序中。所以你必须先“缓存”它。
/* 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" [%postdatar] %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;
【讨论】:
【参考方案2】:我以这种方式解决了这种情况。我创建了一个 util 方法,该方法返回从请求正文中提取的对象,使用能够接收 Reader 的 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;
【讨论】:
什么是 PortalUtil? 我敢打赌这是来自 Liferay,Liferay 特定的 API【参考方案3】:这适用于所有 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;
【讨论】:
【参考方案4】:在 Java 8 中,您可以用一种更简单、更简洁的方式来做到这一点:
if ("POST".equalsIgnoreCase(request.getMethod()))
test = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
【讨论】:
我更喜欢这个解决方案,因为它是纯 Java,没有任何第三方依赖。 但请记住,我们无法再次读取请求正文,因为getReader
已被调用。
我们如何才能将新格式化的 HTTP POST 数据重新设置回请求中?
我已经尝试过这个解决方案,但它引入了一个非常严重的问题,我使用此代码在 oncePerRequest 过滤器中记录请求信息,当我使用它时,我所有的 post 方法中的所有 @modelAttribute 绑定在对象的所有字段中都给出了 null 。我不建议使用这种方法。
@MohammedFathi 你必须创建一个 WebMvcConfigurer 并添加一个拦截器,你可以在其中 HttpServletRequest requestCache = new ContentCachingRequestWrapper(request);然后将 requestCache 对象用于您的日志【参考方案5】:
commons-io 的简单方法。
IOUtils.toString(request.getReader());
https://commons.apache.org/proper/commons-io/javadocs/api-2.5/org/apache/commons/io/IOUtils.html
【讨论】:
如果你能举一个例子说明阅读器的输出是什么样子(即是否显示键),我会提供帮助 这对我有用。我可以确认,此解决方案还避免了 bufferedreader.readLine() 无缘无故“挂起”的常见情况 嗨@DavidDomingo,它100%有效,我可以读到您在上面的回复中评论相同(也有效)。检查代码中的某处(可能是过滤器),或者可能是因为 Spring 的某个人,之前没有调用 getReader() 方法,因为如果你调用它两次或更多次,它只会返回第一个有效负载。 嗨@Dani,这就是它不起作用的原因。读者是空的。我认为 RestController 在您能够在任何端点中执行之前读取它。获取正文的最简单方法是使用 HttpEntity。【参考方案6】:这适用于 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 "";
【讨论】:
【参考方案7】:请注意,您的代码非常嘈杂。 我知道这个线程很旧,但很多人还是会读它。 你可以使用 guava 库来做同样的事情:
if ("POST".equalsIgnoreCase(request.getMethod()))
test = CharStreams.toString(request.getReader());
【讨论】:
或许可以考虑if(RequestMethod.POST.name().equalsIgnoreCase(...)) ...
我得到 java.lang.IllegalStateException: getReader() 已经为此请求调用【参考方案8】:
如果你想要的只是 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 "";
归功于:https://***.com/a/5445161/1389219
【讨论】:
考虑到request.getInputStream()
不像request.getReader()
那样接受请求字符编码。为链接 +1。
PUT 方法的等价物应该是什么?【参考方案9】:
如果请求体是空的,那么它只是意味着它已经被预先消费了。例如,通过request.getParameter()
、getParameterValues()
或getParameterMap()
调用。只需从您的代码中删除执行这些调用的行。
【讨论】:
只有在不是文件上传的情况下才有效,就像curl
示例一样,不是吗?
我试过这个东西。但我仍然没有得到 POST 正文。我肯定错过了什么。补充一点:我正在使用 Tapestry 进行开发。以上是关于从 HttpServletRequest 获取 POST 请求正文的主要内容,如果未能解决你的问题,请参考以下文章
从 HttpServletRequest 获取 AsyncContext
转:Spring Boot 获取 HttpServletRequest 的方法
从 HttpServletRequest 获取 XML 并用于端点