Java.io.IOException: 尝试从关闭的流中读取

27

我正在使用Apache HTTPClient进行HTTPPost调用,然后我尝试使用Jackson从响应中创建一个对象。 这是我的代码:

private static final Logger log = Logger.getLogger(ReportingAPICall.class);
ObjectMapper mapper = new ObjectMapper();

public void makePublisherApiCall(String jsonRequest)
{
    String url = ReaderUtility.readPropertyFile().getProperty("hosturl");
    DefaultHttpClient client = new DefaultHttpClient();
    try {
        HttpPost postRequest = new HttpPost(url);
        StringEntity entity = new StringEntity(jsonRequest);
        postRequest.addHeader("content-type", "application/json");
        log.info("pub id :"+ExcelReader.publisherId);
        postRequest.addHeader("accountId", ExcelReader.publisherId);
        postRequest.setEntity(entity);
        HttpResponse postResponse = client.execute(postRequest);
        log.info(EntityUtils.toString(postResponse.getEntity()));

    //  Response<PublisherReportResponse> response = mapper.readValue(postResponse.getEntity().getContent(), Response.class);
    //  log.info("Reponse "+response.toString());
    } catch (UnsupportedEncodingException ex) {
        log.error(ex.getMessage());
        log.error(ex);
        Assert.assertTrue(false, "Exception : UnsupportedEncodingException");
    } catch (ClientProtocolException ex) {
        log.error(ex.getMessage());
        log.error(ex);
        Assert.assertTrue(false, "Exception : ClientProtocolException");
    } catch (IOException ex) {
        log.error(ex.getMessage());
        log.error(ex);
        Assert.assertTrue(false, "Exception : IOException");
    }

调用makePublisherApiCall()方法将在一个循环中运行,例如100次。问题基本上是当我取消注释该行时出现:

//  Response<PublisherReportResponse> response = mapper.readValue(postResponse.getEntity().getContent(), Response.class);
//  log.info("Reponse "+response.toString());

取消注释后,我遇到了异常:

Attempted read from closed stream.
17:26:59,384 ERROR com.inmobi.reporting.automation.reportingmanager.ReportingAPICall - java.io.IOException: Attempted read from closed stream.
否则它正常工作。请问有人能告诉我我做错了什么吗?
6个回答

61
EntityUtils.toString(postResponse.getEntity())方法会对响应实体进行处理,我猜测它会消耗实体的内容流。根据HttpClient javadoc的说明,只有可重复使用的实体才能被多次消耗。因此,如果实体不可重复,则无法再次将内容流提供给映射器。为了避免这种情况,你应该只让映射器消耗流——如果需要记录内容,则记录解析后的响应对象。

但我认为这两行代码引起了问题。 // Response<PublisherReportResponse> response = mapper.readValue(postResponse.getEntity().getContent(), Response.class); // log.info("Reponse "+response.toString()); 在注释掉这些代码后,程序可以正常工作。 - Pratik
3
好的,如果我理解正确,问题在于您只能 一次 消费实体的内容。我怀疑您正在通过日志记录和解析进行两次消费。如果您想要检查我的怀疑是否正确,请将 log.info(EntityUtils.toString(postResponse.getEntity())); 注释掉,并取消注释以下两行代码,然后再次检查是否正常运行。 - Pyranja

15

我曾经遇到相同的问题。确保你不是在IDE的“watch”或“inspect”部分中消费实体内容流。这会导致内容流被关闭(读取后)。

对于我的英语表示抱歉。


这个问题困扰了我很久,一开始就抛出异常,让我很纳闷。后来我意识到是因为我在变量监视器中添加了“httpResponse.getEntity()”。感谢您的答案,这节省了我很多时间。 - Chintan Patel

14

我在这里找到了与Spring RestTemplate类似问题的答案:https://www.baeldung.com/spring-rest-template-interceptor

如果我们希望拦截器能够作为请求/响应记录器运行,则需要读取两次 - 第一次是通过拦截器,第二次是通过客户端。

默认实现只允许我们读取响应流一次。为了应对这种特定情况,Spring提供了一个名为BufferingClientHttpRequestFactory的特殊类。如其名称所示,该类将在JVM内存中缓存请求/响应以供多次使用。

以下是使用BufferingClientHttpRequestFactory初始化RestTemplate对象并启用请求/响应流缓存的方法:

RestTemplate restTemplate = new RestTemplate( new BufferingClientHttpRequestFactory( new SimpleClientHttpRequestFactory() ) );

2

我遇到过同样的问题。

这个想法是,如果你使用了postResponse,那么你应该把它放在一个变量中,以便在不同的地方再次使用。 否则,连接会关闭,你就不能再次使用相同的响应了。

我曾经记录它(用于调试目的),但总是失败。


0
在我的情况下,这个问题与另一个原因有关, 我遇到了这个问题,是因为我没有使用

closeableresponce=client.Getmethod(FinalUrl);

在我的第一个Test1中,它被提到了,但是当我在Test2中错过时,我忘记放置这段代码,这就是为什么流关闭消息会显示的原因...
public void getapiwithheader() throws ParseException, IOException
{
    client = new RestClient();
    closeableresponce=client.Getmethod(FinalUrl);

    HashMap<String, String> headermap = new HashMap<>();
    headermap.put("content-type", "application/json");
    //****************************************************************************    
    //Status code
    //****************************************************************************      
    int statuscode =closeableresponce.getStatusLine().getStatusCode();
    System.out.println("Status code "+statuscode);
    Assert.assertEquals(statuscode,RESPONSE_STATUS_CODE_200, "Status code is not 200");


    //****************************************************************************  
    // Json String
    //****************************************************************************      
    String responsestring=  EntityUtils.toString(closeableresponce.getEntity(),"UTF-8");
    JSONObject respjsonobj = new JSONObject(responsestring);
    System.out.println("Respose Json API"+respjsonobj);

    //****************************************************************************      
    // Verify the value of the JSON
    //****************************************************************************      
    String Email =TestUtil.getValueByJPath(respjsonobj,"/email");
    System.out.println("******************************************");
    System.out.println("Print the value of Email "+Email);
    Assert.assertEquals(Email, "johndoe@google.com");
}

-2

我遇到了同样的问题。

在我的情况下,我需要通过AOP获取响应内容。


欢迎来到Stack Overflow。如果您能详细说明一下 我需要通过AOP获取响应内容,那将更好。请注意,您正在回答一个旧的已经有答案的问题(带有绿色勾号的答案),而您当前的回答更像是评论而不是答案。(Stack Overflow的问答格式并不适用于论坛式讨论)。- 请阅读如何撰写优秀的答案?并参观tour以了解Stack Overflow的工作方式。 - Ivo Mori

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接