为啥 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-urlencodedRequest 继续:

    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-&gt;getInputStream() 之前的代码中设置断点。 进入HttpServletRequest-&gt;getInputStream() 方法,现在您在某个...Impl 类中。 在 getInputStream() 实现中设置新断点,甚至在其 read() 方法中设置。 重复测试调用,看看是什么消耗了您的数据。

【讨论】:

【参考方案7】:

我在 Spring Boot 2.2.1 项目中为 org.springframework 启用调试日志记录时遇到了问题,因此使用了 spring-webmvc 5.2.1。

这是由参数映射的请求日志记录引起的,如果Content-Typeapplication/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详解

java HttpServletRequest 重复流读取

为啥 HttpServletRequest.getRemoteAddr() 在 Java servlet 中不起作用? [复制]