【问题标题】:RestTemplate postForEntity experiencing java.io.IOException: Premature EOF errorRestTemplate postForEntity 遇到 java.io.IOException: Premature EOF 错误
【发布时间】:2017-09-11 03:35:02
【问题描述】:

如果我在我的一个 maven 项目中遇到任何问题,我们将不胜感激。

Exception in thread "main" org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://test-services.domain.ph/campaign/": Premature EOF; nested exception is java.io.IOException: Premature EOF
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:666)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:407)
at homecredit.ph.CampaignConnector.call(CampaignConnector.java:46)
Caused by: java.io.IOException: Premature EOF
at sun.net.www.http.ChunkedInputStream.readAheadBlocking(ChunkedInputStream.java:565)
at sun.net.www.http.ChunkedInputStream.readAhead(ChunkedInputStream.java:609)
at sun.net.www.http.ChunkedInputStream.read(ChunkedInputStream.java:696)
at java.io.FilterInputStream.read(FilterInputStream.java:133)

产地:

ResponseEntity<ApiResponse> response = restTemplate.postForEntity(url, entity, ApiResponse.class);

目的地:

@RequestMapping(value="/campaign", method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ApiResponse> insertCampaignRecord(
        @Valid @RequestBody CampaignRecordInsertRequest campaignRecordInsertRequest){
    logInfo("Incoming insert request. " + DescriptorUtility.convertToString(campaignRecordInsertRequest));
    campaignDataService.insertNewRecord(CampaignRecordConverter.convertToCampaignRecord(campaignRecordInsertRequest));
    return ResponseUtility.defaultResponse();
}

响应实用程序

public static ResponseEntity<ApiResponse> defaultResponse(){
    ApiResponse apiResponse = new ApiResponse();
    apiResponse.setTimestamp(DateUtility.currentDateString());
    apiResponse.setMessage(ResponseMessages.SUCCESS);
    return new ResponseEntity<>(apiResponse, HttpStatus.OK);
}

活动数据服务

@Async("AsyncExecutor")
public void insertNewRecord(CampaignRecord campaignRecord) {
    try {
        campaignRecordRepository.save(campaignRecord);
    } catch (Exception e) {
        logError(e);
    }
}

服务器日志

2017-09-11 11:11:11  INFO 18383 [http-nio-8773-exec-10] [CampaignRecordController] - Incoming insert request. {"dateCampaign":1504656000000,"cuid":...
2017-09-11 11:11:11  WARN 18383 [http-nio-8773-exec-10] [SqlExceptionHelper] - SQL Error: 1062, SQLState: 23000
2017-09-11 11:11:11 ERROR 18383 [http-nio-8773-exec-10] [SqlExceptionHelper] - Duplicate entry 'CMP_CLX##1208637#20170906' for key 'UNIQUE_KEY'
2017-09-11 11:11:11 ERROR 18383 [http-nio-8773-exec-10] [CampaignDataService] - could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
2017-09-11 11:11:11 ERROR 18383 [http-nio-8773-exec-10] [CampaignDataService] - could not execute statement

PS。服务器日志正常(返回成功响应记录是否保存成功)

问题是间歇性的。发送批量请求时随机发生。

提前致谢! :)

【问题讨论】:

  • 这些是客户端日志,您在服务器日志中看到的任何痕迹?
  • 用日志更新了问题描述。但是服务器是异步设计的,所以无论是否失败,它都必须返回一个成功的响应。
  • 也分享campaignDataService.insertNewRecord代码。
  • 添加了campaignDataService.insertNewRecord 代码。已尝试删除异步实现,但问题仍然存在。

标签: java spring maven spring-boot


【解决方案1】:

我在 Spring Boot 2.1 中遇到了同样的问题。就我而言,我有 3 个应用程序(称它们为 A、B 和 C),其中 B 实际上只是 A 和 C 之间的代理:

A --> B --> C

Premature EOF 发生在 from B to A 的响应中。所有指示都是成功的 HTTP 响应 (200),但使用检查响应的正文调试器显示它在序列化 DTO 数据的 中间 中有一个换行符,而不是在我预期的末尾:

(注意id 字段后面的返回字符,并且缺少任何内容长度;忽略末尾的不可读框,它们是未初始化/未使用的字节数组的一部分)

就我而言,服务 B 既是服务器又是客户端。代码看起来像这样:

public ResponseEntity<String> handle(String request, HttpHeaders headers) {

    // Do some validation and then call Service C, and pass the response
    // back to Service A

    return restTemplate.postForEntity(
        urlForServiceC,
        new HttpEntity<>(request, headers),
        String.class);
}

我没有深入了解 RestTemplate 或其消息转换器的内容,但提示我响应缓冲可能存在问题的是我使用 Spring filter 来记录响应的每项服务。此过滤器必须复制响应流,以避免其他过滤器与已使用的正文相关的异常。

我注意到,当我使用此过滤器启用 运行时,Premature EOF 异常消失了。当我禁用它时,异常又回来了。关于复制响应流的一些事情已经解决了Premature EOF 错误。

这导致我在服务 B 中尝试以下操作:

public ResponseEntity<String> handle(String request, HttpHeaders headers) {

    // Do some validation and then call Service C, and pass the response
    // back to Service A

    String response = restTemplate.postForEntity(
        urlForServiceC,
        new HttpEntity<>(request, headers),
        String.class).getBody();

    return ResponseEntity.ok(response);
}

细微的变化是我首先将响应保存到局部变量,这需要我调用ResponseEntity.getBody()。这会强制使用来自服务 C 的整个响应主体,然后再返回到服务 A。进行此更改后,我的 Premature EOF 错误没有返回。

【讨论】:

    【解决方案2】:

    根据服务器日志看起来,服务器正在尝试保存一些记录并且它失败了(由于唯一键违规)。

    2017-09-11 11:11:11  WARN 18383 [http-nio-8773-exec-10] [SqlExceptionHelper] - SQL Error: 1062, SQLState: 23000
    2017-09-11 11:11:11 ERROR 18383 [http-nio-8773-exec-10] [SqlExceptionHelper] - Duplicate entry 'CMP_CLX##1208637#20170906' for key 'UNIQUE_KEY'
    2017-09-11 11:11:11 ERROR 18383 [http-nio-8773-exec-10] [CampaignDataService] - could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
    2017-09-11 11:11:11 ERROR 18383 [http-nio-8773-exec-10] [CampaignDataService] - could not execute statement
    

    看起来服务器无法正常处理异常并且整个流程中断,导致 HTTP-500 响应代码(可能)带有空响应。

    您可以采取两种行动:

    1. 优雅地处理异常。
    2. 验证唯一密钥被违反的原因并在可能的情况下进行修复。

    【讨论】:

    • 嗨,如我的控制器上所示,无论数据插入后的结果如何,我都会返回一个 FIXED 响应,所以我认为这不是问题 :)
    • 代码被包裹在 try-catch 下,所以异常不会传播。下一步是以字符串格式记录响应,不要尝试一步将其包装在 ApiResponse 下,这将帮助您识别从服务器接收到的内容。另外,如果您在 Servlet 3.1 上使用异步控制器(例如 Callable 或 DeferredResult),为什么要将服务方法设为 @Async
    • 赞成让我意识到与 ResponseEntity 核对。尽管问题似乎无关紧要,但无论如何,谢谢!不早点指出是我的坏事。
    【解决方案3】:

    对于任何可能遇到这种情况的人。 问题是由 spring boot eureka 引起的。 在大规模(批量处理)传递 ResponseEntity 响应时似乎存在错误,导致响应状态格式错误。

    当前的解决方法是从 ResponseEntity 切换到对象主体。

    【讨论】:

      【解决方案4】:

      当我使用带有默认 http 客户端工厂的 RestTemplate 时,我遇到了同样的问题。我在捕获数据包时发现它在标头中缺少“Accept-Encoding:gzip”。最后我通过用 apache 替换默认的 http 客户端工厂来得到它http客户端工厂,像这样:

      HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create().build());
      RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
      

      【讨论】:

        猜你喜欢
        • 2021-11-30
        • 2017-10-01
        • 2016-11-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-18
        相关资源
        最近更新 更多