jjzjj

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

想太多会累i 2024-01-19 原文

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

错误出现

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

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报错cn.hutool.http.HttpException: Read timed out的更多相关文章

  1. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  2. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  3. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  4. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  5. ruby-on-rails - Rails - 从命名路由中提取 HTTP 动词 - 2

    Rails中有没有一种方法可以提取与路由关联的HTTP动词?例如,给定这样的路线:将“users”匹配到:“users#show”,通过:[:get,:post]我能实现这样的目标吗?users_path.respond_to?(:get)(显然#respond_to不是正确的方法)我最接近的是通过执行以下操作,但它似乎并不令人满意。Rails.application.routes.routes.named_routes["users"].constraints[:request_method]#=>/^GET$/对于上下文,我有一个设置cookie然后执行redirect_to:ba

  6. ruby-on-rails - Heroku 吃掉了我的自定义 HTTP header - 2

    我正在使用Heroku(heroku.com)来部署我的Rails应用程序,并且正在构建一个iPhone客户端来与之交互。我的目的是将手机的唯一设备标识符作为HTTPheader传递给应用程序以进行身份​​验证。当我在本地测试时,我的header通过得很好,但在Heroku上它似乎去掉了我的自定义header。我用ruby​​脚本验证:url=URI.parse('http://#{myapp}.heroku.com/')#url=URI.parse('http://localhost:3000/')req=Net::HTTP::Post.new(url.path)#boguspara

  7. ruby-on-rails - 使用 HTTP.get_response 检索 Facebook 访问 token 时出现 Rails EOF 错误 - 2

    我试图在我的网站上实现使用Facebook登录功能,但在尝试从Facebook取回访问token时遇到障碍。这是我的代码:ifparams[:error_reason]=="user_denied"thenflash[:error]="TologinwithFacebook,youmustclick'Allow'toletthesiteaccessyourinformation"redirect_to:loginelsifparams[:code]thentoken_uri=URI.parse("https://graph.facebook.com/oauth/access_token

  8. ruby - HTTP 请求中的用户代理,Ruby - 2

    我是Ruby的新手。我试过查看在线文档,但没有找到任何有效的方法。我想在以下HTTP请求botget_response()和get()中包含一个用户代理。有人可以指出我正确的方向吗?#PreliminarycheckthatProggitisupcheck=Net::HTTP.get_response(URI.parse(proggit_url))ifcheck.code!="200"puts"ErrorcontactingProggit"returnend#Attempttogetthejsonresponse=Net::HTTP.get(URI.parse(proggit_url)

  9. ruby - 如何使用 Ruby HTTP::Net 处理 404 错误? - 2

    我正在尝试解析网页,但有时会收到404错误。这是我用来获取网页的代码:result=Net::HTTP::getURI.parse(URI.escape(url))如何测试result是否为404错误代码? 最佳答案 像这样重写你的代码:uri=URI.parse(url)result=Net::HTTP.start(uri.host,uri.port){|http|http.get(uri.path)}putsresult.codeputsresult.body这将打印状态码和正文。

  10. ruby - 让 bundler 使用 http : instead of git:? - 2

    我正在安装gitlabhq,并且在Gemfile中有对某些资源的“git://...”的引用。但是,我在公司防火墙后面,所以我必须使用http://。我可以手动编辑Gemfile,但我想知道是否有另一种方法告诉bundler使用http://作为git存储库? 最佳答案 您可以通过运行gitconfig--globalurl."https://".insteadOfgit://或通过将以下内容添加到~/.gitconfig:[url"https://"]insteadOf=git://

随机推荐