HttpClient报错cn.hutool.http.HttpException: Read timed out

Posted 想太多会累i

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HttpClient报错cn.hutool.http.HttpException: Read timed out相关的知识,希望对你有一定的参考价值。

这个错误明面上说是请求等待超时,但是其实真正的问题并不一定是等待超时的问题

错误出现

这个错误出现是一个项目更新以后的出现的这个问题,错误的代码如下:

cn.hutool.http.HttpException: Read timed out
	at cn.hutool.http.HttpResponse.init(HttpResponse.java:511)
	at cn.hutool.http.HttpResponse.initWithDisconnect(HttpResponse.java:484)
	at cn.hutool.http.HttpResponse.<init>(HttpResponse.java:81)
	at cn.hutool.http.HttpRequest.doExecute(HttpRequest.java:1130)
	at cn.hutool.http.HttpRequest.execute(HttpRequest.java:1012)
	at cn.hutool.http.HttpRequest.execute(HttpRequest.java:988)
	at cn.zc.sport.medical.http.HuHttpUtil.httpPost(HuHttpUtil.java:41)
	at cn.zc.sport.medical.task.RequestSending.core(RequestSending.java:138)
	at cn.zc.sport.medical.task.RequestSending.sending(RequestSending.java:77)
	at sun.reflect.GeneratedMethodAccessor35.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84)
	at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
	at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
	at java.util.concurrent.FutureTask.runAndReset(Unknown Source)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(Unknown Source)
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(Unknown Source)
	at java.net.SocketInputStream.read(Unknown Source)
	at java.net.SocketInputStream.read(Unknown Source)
	at sun.security.ssl.SSLSocketInputRecord.read(Unknown Source)
	at sun.security.ssl.SSLSocketInputRecord.readHeader(Unknown Source)
	at sun.security.ssl.SSLSocketInputRecord.bytesInCompletePacket(Unknown Source)
	at sun.security.ssl.SSLSocketImpl.readApplicationRecord(Unknown Source)
	at sun.security.ssl.SSLSocketImpl.access$300(Unknown Source)
	at sun.security.ssl.SSLSocketImpl$AppInputStream.read(Unknown Source)
	at java.io.BufferedInputStream.fill(Unknown Source)
	at java.io.BufferedInputStream.read1(Unknown Source)
	at java.io.BufferedInputStream.read(Unknown Source)
	at sun.net.www.http.HttpClient.parseHTTPHeader(Unknown Source)
	at sun.net.www.http.HttpClient.parseHTTP(Unknown Source)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(Unknown Source)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
	at java.net.HttpURLConnection.getResponseCode(Unknown Source)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(Unknown Source)
	at cn.hutool.http.HttpConnection.responseCode(HttpConnection.java:470)
	at cn.hutool.http.HttpResponse.init(HttpResponse.java:508)
	... 20 common frames omitted

这里解释一下,因为项目使用了hutool这个工具包,并且http请求也是用的hutool包中的HttpRequest,但是其实hutool中的HttpRequest是对httpClient做了封装本质上也是HttpClient报的错

接下来说一下原因:需求是这样的,我们需要做一个服务将这个电脑上的数据和文件发送给服务器,因此发送采用了hotool工具包HttpRequest,一开始项目是没有问题的,但是后面服务器项目更新了之后就出现了这个问题

分析的过程

最早的分析是:

String result = HttpRequest.post(url)
                .header("contentType", "multipart/form-data")
                .form(paramFile)
                .timeout(20000) // 请求等待时间
                .execute().body();

我们认为这个请求超时的时间设置的太短了,因此引发了这个故障,于是我们把原来的20000 设置成了 60000 也就是 一分钟,部署之后服务又开始正常运行了,可是没有几天他又出现了这个问题,因为出现了一个数据量很大的文件,这个文件的处理超过了一分钟,他又报错了,紧接着我设置了2分钟,但是这次的代码审核没有通过,原因是,可能会存在更大的文件。并且在审核的时候拿了一个很大的文件,确实Read Time out又出现了。

紧接着研究了很长时间,最后项目经理给我了思路,他说为什么会这么长的时间,这些时间到底做了什么?

然后,我通过日志分析了整个这个请求的所有的流程,终于发现了问题出在哪里,因为这个请求是发送给另一个项目的接口,一两句话说不清楚,下面用一个简单的图描述一下这个流程:


其实当时分析完这个之后,我想到了使用异步MQ的方式,但是这个提议被否决了,原因是需要搭建MQ服务,成本太大。

最终解决方案

最后分析的解决的方式是这样的:

其实在数据服务器接收到文件的时候,上一个数据推送服务的工作已经结束了,他就没必要等了,所以这里采用了异步调用的方式,来执行,后面的文件的处理

这里写一下异步的实现方式:

首先写一个接口类:

/**
 * 异步处理DCM文件
 *
 * @author An
 * @date 2022/11/3 17:59
 */
public interface AsyncDcm 
    @Async
    void DCMDetailWith(MultipartFile multipartFiles, String decrypt, String study_uid);

写class实现这个接口:

/**
 * 异步处理DCM文件实现类
 *
 * @author An
 * @date 2022/11/3 18:01
 */
@Log
@Service
public class AsyncDcmImpl implements AsyncDcm 
    @Autowired
    private Zip4jUtil zip4jUtil;

    @Autowired
    private FileExists fileExists;

    @Value("$file.zipPath")
    private String zipPath;

    @Value("$file.filePath")
    private String filePath;

    @Value("$file.jpgPath")
    private String jpgPath;

    @Autowired
    private OrthancPacsRestTemplate pacsRestTemplate;


    @Override
    public void DCMDetailWith(MultipartFile multipartFiles, String decrypt, String study_uid) 
        // 图片影像dcm文件处理
        if(multipartFiles != null) 
            // 创建缓存目录
            fileExists.directory(zipPath);
            fileExists.directory(filePath);
            fileExists.directory(jpgPath);
            // 清理缓存
            //zip4jUtil.emptyFile(zipPath);
            zip4jUtil.emptyFile(filePath);
            //zip4jUtil.emptyFile(jpgPath);
            // 接收压缩包 文件缓存到本地

            log.info("影像数据文件 ->" + decrypt + " " + multipartFiles.getOriginalFilename());

            File zipFile = new File(zipPath + multipartFiles.getOriginalFilename());
            try 
                //读取zipFile文件
                multipartFiles.transferTo(zipFile);
             catch (IOException e) 
                e.printStackTrace();
            
            // 解压
            zip4jUtil.unzip(zipPath + zipFile.getName(), filePath + study_uid);
            // 解压后的dcm文件
            List<File> fileList = fileExists.search(filePath + study_uid + "/", new ArrayList<>());
            //上传图片到PACS
            for (File JpgFile : fileList) 
                pacsRestTemplate.sendInstances(JpgFile);
                log.info("发送DICOM图像至Orthanc ->" + JpgFile.getName());
            
        
    

然后在controller层中调用异步方式处理文件:
注意:在controller上添加 @EnableAsync 注解

package cn.stylefeng.guns.modular.api.controller;

import cn.stylefeng.guns.entity.Study;
import cn.stylefeng.guns.modular.api.async.AsyncDcm;
import cn.stylefeng.guns.modular.utils.AESUtil;
import cn.stylefeng.guns.modular.utils.FileExists;
import cn.stylefeng.guns.modular.utils.Zip4jUtil;
import cn.stylefeng.guns.sys.modular.system.entity.User;
import cn.stylefeng.guns.sys.modular.system.service.UserService;
import cn.stylefeng.guns.util.OrthancPacsRestTemplate;
import cn.stylefeng.roses.core.util.ToolUtil;
import cn.stylefeng.roses.kernel.model.response.ResponseData;
import lombok.extern.java.Log;

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zc.workflow.form.handle.service.HandleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.*;

@Controller
@Log
@EnableAsync
@RequestMapping("/api")
public class HandleEquipController 

    @Autowired
    private HandleService handleService;

    @Autowired
    private UserService userService;

    @Autowired
    private AESUtil aesUtil;

    @Autowired
    private Zip4jUtil zip4jUtil;

    @Autowired
    private FileExists fileExists;

    @Value("$file.zipPath")
    private String zipPath;

    @Value("$file.filePath")
    private String filePath;

    @Value("$file.jpgPath")
    private String jpgPath;

    @Autowired
    private AsyncDcm asyncDcm;


    /**
     * 接收影像设备检查结果:修改表单中图片影像、影像结果
     * @param enData 密文
     * @param multipartFiles  dcm文件压缩包
     * @return
     */
    @RequestMapping(method = RequestMethod.POST, path="/acceptEquipmentResult")
    @ResponseBody
    public ResponseData acceptEquipmentResult(@RequestParam("enData") String enData,
                                              @RequestParam(value = "files", required = false) MultipartFile multipartFiles) 
    	
    	log.info("影像接收到密文 -> " + enData);
    	
        String allOrTaskForm = "allForm";
        String msg = "yes";
        // 解密
        String decrypt = aesUtil.decrypt(enData);
        
        log.info("影像数据解密 ->" + decrypt);
        
        // 解析为json数组
        JSONArray jsonArray = JSONArray.parseArray(decrypt);
        // 装入Study List集合
        List<Study> addList = jsonArray.toJavaList(Study.class);
        // 流程任务单据编号
        String processTaskCode = "";
        // 临床诊断
        String clinicalDiagnosis = "";
        // 用户uid (用于文件存储操作)
        String study_uid = "";
        // 操作用户
        String operateName = null;
        // 遍历集合 拿出数据
        for (Study study : addList) 
            processTaskCode = study.getPatient_id();
            clinicalDiagnosis = study.getFinding();
            if (study.getReporter() != null) 
                operateName = study.getReporter();
            
            study_uid = study.getStudy_uid();
        
        // 异步处理接收DCM文件
        asyncDcm.DCMDetailWith(multipartFiles, decrypt, study_uid);
        return ResponseData.success(result);
    

最后在启动类上添加@EnableAsync 注解

测试

2022-07-22 15:29:52.121  INFO 4388 --- [scheduling-1] cn.zc.sport.medical.http.HuHttpUtil      : >>>>>>>>>> 发送完成,耗时:2822 ms <<<<<<<<<<

完美解决

总结

出现这个问题很多时候需要分析发出去请求之后等待的时间服务到底做了什么,如果确实都没发过去,那再调整超时时间,不然可以使用异步的方式缩短处理时间,还不行,推荐使用MQ异步消息队列。

httpclient的一个错误

这是以下报错信息,org.apache.http.ConnectionClosedException: Premature end of Content-Length delimited message body (expected: 44541; received: 44539

报错代码:EntityUtils.toByteArray(entity)
HttpResponse response = mClient.execute(method);
HttpEntity entity = entity = response.getEntity();
EntityUtils.toByteArray(entity)
以下问题困扰小弟很久了,还望各位大虾鼎力相助,小弟感激不尽。

参考技术A ConnectionClosedException连接关闭异常,你是连自己的还是连别的页面的追问

当然是连接别人的页面啊。这个问题怎么解决呢

以上是关于HttpClient报错cn.hutool.http.HttpException: Read timed out的主要内容,如果未能解决你的问题,请参考以下文章

HttpClient报错Timeout waiting for connection from pool

Android SDK使用HttpClient的问题,为啥老是报错,困扰了我好几天,求大神解救!

HttpClient报错cn.hutool.http.HttpException: Read timed out

(多张图片打包为Zip返回前端下载) 记NetCore HttpClient.GetStreamAsync()返回只读流,Stream的Length属性不可用,报错的问题。

httpclient的一个错误

as使用 AsyncHttpClient 报错的问题,是因为版本新了,而android6.9 就把HttpClient给删除了