为啥 HttpServletRequest 输入流为空?
Posted
技术标签:
【中文标题】为啥 HttpServletRequest 输入流为空?【英文标题】:Why is HttpServletRequest inputstream empty?为什么 HttpServletRequest 输入流为空? 【发布时间】:2012-01-21 07:05:05 【问题描述】:我有这段代码,我从请求输入流中读取输入并使用 JacksonMapper 转换为 POJO。它在带有 guice 支持的 jetty 7 容器中运行。
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
try
RequestType requestType = mapper.readValue(req.getInputStream(), RequestType.class);
Catch(Exception ex)
....
但是,有时在负载下会引发以下异常。我检查了我的客户,我确信它发送了一个有效的 json 字符串。出了什么问题?这是 Jetty 7 在负载下的预期行为吗?
java.io.EOFException: No content to map to Object due to end of input
at org.codehaus.jackson.map.ObjectMapper._initForReading(ObjectMapper.java:2433)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2385)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1637)
at com.ea.wsop.user.LoginServlet.processRequest(LoginServlet.java:69)
at com.ea.wsop.user.LoginServlet.doPost(LoginServlet.java:63)
at com.ea.wsop.user.LoginServlet$$EnhancerByGuice$$a91c2ebd.CGLIB$doPost$0(<generated>)
at com.ea.wsop.user.LoginServlet$$EnhancerByGuice$$a91c2ebd$$FastClassByGuice$$c6f479ee.invoke(<generated>)
at com.google.inject.internal.cglib.proxy.$MethodProxy.invokeSuper(MethodProxy.java:228)
at com.google.inject.internal.InterceptorStackCallback$InterceptedMethodInvocation.proceed(InterceptorStackCallback.java:72)
at com.ea.monitor.MethodExecutionTimer.invoke(MethodExecutionTimer.java:130)
at com.google.inject.internal.InterceptorStackCallback$InterceptedMethodInvocation.proceed(InterceptorStackCallback.java:72)
at com.google.inject.internal.InterceptorStackCallback.intercept(InterceptorStackCallback.java:52)
at com.ea.wsop.user.LoginServlet$$EnhancerByGuice$$a91c2ebd.doPost(<generated>)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
at com.ea.wsop.user.LoginServlet$$EnhancerByGuice$$a91c2ebd.CGLIB$service$8(<generated>)
at com.ea.wsop.user.LoginServlet$$EnhancerByGuice$$a91c2ebd$$FastClassByGuice$$c6f479ee.invoke(<generated>)
at com.google.inject.internal.cglib.proxy.$MethodProxy.invokeSuper(MethodProxy.java:228)
at com.google.inject.internal.InterceptorStackCallback$InterceptedMethodInvocation.proceed(InterceptorStackCallback.java:72)
at com.ea.monitor.MethodExecutionTimer.invoke(MethodExecutionTimer.java:130)
at com.google.inject.internal.InterceptorStackCallback$InterceptedMethodInvocation.proceed(InterceptorStackCallback.java:72)
at com.google.inject.internal.InterceptorStackCallback.intercept(InterceptorStackCallback.java:52)
at com.ea.wsop.user.LoginServlet$$EnhancerByGuice$$a91c2ebd.service(<generated>)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
at com.ea.wsop.user.LoginServlet$$EnhancerByGuice$$a91c2ebd.CGLIB$service$9(<generated>)
at com.ea.wsop.user.LoginServlet$$EnhancerByGuice$$a91c2ebd$$FastClassByGuice$$c6f479ee.invoke(<generated>)
at com.google.inject.internal.cglib.proxy.$MethodProxy.invokeSuper(MethodProxy.java:228)
at com.google.inject.internal.InterceptorStackCallback$InterceptedMethodInvocation.proceed(InterceptorStackCallback.java:72)
at com.ea.monitor.MethodExecutionTimer.invoke(MethodExecutionTimer.java:130)
at com.google.inject.internal.InterceptorStackCallback$InterceptedMethodInvocation.proceed(InterceptorStackCallback.java:72)
at com.google.inject.internal.InterceptorStackCallback.intercept(InterceptorStackCallback.java:52)
at com.ea.wsop.user.LoginServlet$$EnhancerByGuice$$a91c2ebd.service(<generated>)
at com.google.inject.servlet.ServletDefinition.doService(ServletDefinition.java:263)
【问题讨论】:
【参考方案1】:如果事先已经消耗,它将是空的。每当您在HttpServletRequest
上调用getParameter()
、getParameterValues()
、getParameterMap()
、getReader()
等时,都会隐式执行此操作。确保在调用getInputStream()
之前,您不调用任何本身需要从请求正文中收集信息的方法。如果您的 servlet 没有这样做,请开始检查映射到相同 URL 模式的 servlet 过滤器。
更新:这似乎是 GAE 1.5 特有的。另请参阅
http://code.google.com/p/googleappengine/issues/detail?id=5161 http://code.google.com/p/googleappengine/issues/detail?id=5898恐怕在他们修复之前没有解决方案/解决方法。您可以尝试检查它是否在Filter
中可用,如果是,则将其复制并存储为请求属性。但这可能会影响某些 GAE servlet 的进一步处理。
【讨论】:
我的代码中没有任何东西在消耗它,但 Guice 可能正在做一些事情。 您可以尝试改用getQueryString()
,我只是不确定GAE如何处理“语法无效”的查询字符串。
这是一个发布请求,我正在从那里提取数据。
你永远不会知道 GAE 是个奇怪的野兽。至少,我使用“gae request getinputstream”搜索并发现了几个类似的问题和错误报告。我将其包含在我的答案中。
这是在 jetty 7 的 ec2 实例上运行,而不是应用引擎。【参考方案2】:
在 Jetty 6.1.15 中我的请求 InputStream 总是为空的问题,发现这是由于缺少或错误的“Content-Type”标头引起的。
我使用 HttpUrlConnection 在另一个 Java 程序中生成请求。当我没有显式设置 Content-Type 标头时,接收程序中request.getInputStream()
返回的InputStream
始终为空。当我将内容类型设置为“二进制/八位字节流”时,请求的InputStream
包含正确的数据。
在getInputStream()
之前对请求对象调用的唯一方法是getContentLength()
。
【讨论】:
【参考方案3】:我使用的是 mod_jk 1.2.39,它有一个导致此问题的错误。更新到 1.2.40 后,它开始工作了。
【讨论】:
【参考方案4】:我在运行 Spring Boot 应用程序时遇到了类似的问题。我的 Spring Boot 应用程序是一个简单的 Dispatcher
servlet,它读取请求正文并对其进行处理。
在我的例子中,如果 curl 命令行使用 -d some-data
并且没有通过-Hcontent-type=some-other-media-type
.
在 Spring Boot 运行的 Apache Catalina servlet 引擎中,Request
类在 parseParameters()
中进行以下测试
if (!("application/x-www-form-urlencoded".equals(contentType)))
success = true;
return;
对于其他 content-type
值,Request
在此处返回,完成。
但是,如果内容类型匹配 application/x-www-form-urlencoded
,Request
继续:
try
if (readPostBody(formData, len) != len)
parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE);
return;
catch (....)
这将消耗身体。因此,在我的情况下,即使 my servlet 除了调用 request.getInputStream()
并尝试从中调用 read()
之外什么都不做,但已经太晚了 - 运行时 Request
已经读取了输入并执行了不缓冲或未读它。唯一的解决方法是设置不同的Content-Type
。
罪魁祸首是
OrderedHiddenHttpMethodFilter(HiddenHttpMethodFilter).doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain)
第70行
它正在寻找"_method"
查询参数。
我可以通过添加禁用过滤器
@Bean
public FilterRegistrationBean registration(HiddenHttpMethodFilter filter)
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
(用于解决another problem)
【讨论】:
这是一个很棒的发现,否则,控制器的 Post 处理程序中的 HttpServletRequest 总是无故返回空。 这是一个艰难的过程,2 小时试图找出帖子实体为空的原因。非常感谢!【参考方案5】:我的帖子遇到了这个问题。我通过在读取参数之前首先读取输入流并将其放入缓存来解决它。这似乎可以解决问题
【讨论】:
【参考方案6】:系统的方法是:
-
获取您的容器的源代码,或者至少是它的 Web 部件(可能很难找到),导入您的 IDE。
在调用
HttpServletRequest->getInputStream()
之前的代码中设置断点。
进入HttpServletRequest->getInputStream()
方法,现在您在某个...Impl 类中。
在 getInputStream()
实现中设置新断点,甚至在其 read()
方法中设置。
重复测试调用,看看是什么消耗了您的数据。
【讨论】:
【参考方案7】:我在 Spring Boot 2.2.1 项目中为 org.springframework
启用调试日志记录时遇到了问题,因此使用了 spring-webmvc 5.2.1。
这是由参数映射的请求日志记录引起的,如果Content-Type
是application/x-www-form-urlencoded
,它会读取输入流。我相信this spring issue与它有关。
请参阅以下导致问题的代码。
private void logRequest(HttpServletRequest request)
LogFormatUtils.traceDebug(logger, traceOn ->
String params;
if (isEnableLoggingRequestDetails())
params = request.getParameterMap().entrySet().stream()
.map(entry -> entry.getKey() + ":" + Arrays.toString(entry.getValue()))
.collect(Collectors.joining(", "));
else
params = (request.getParameterMap().isEmpty() ? "" : "masked");
...
source
我最终报告了an issue,并改为更改请求中的内容类型。
【讨论】:
以上是关于为啥 HttpServletRequest 输入流为空?的主要内容,如果未能解决你的问题,请参考以下文章
解决HttpServletRequest的输入流只能读取一次的问题
解决HttpServletRequest的输入流只能读取一次的问题
解决HttpServletRequest的输入流只能读取一次的问题
HttpServletRequest和HttpServletResponse详解
为啥 HttpServletRequest.getRemoteAddr() 在 Java servlet 中不起作用? [复制]