当前位置: 首页 > news >正文

一次粗心导致的bug定位

一次神奇的Bug定位之旅:从"Remote host terminated the handshake"到URL拼写错误

背景

最近在维护一个PubMed文献下载系统时,遇到了一个非常有趣的Bug。系统在下载PDF文件时频繁出现"Remote host terminated the handshake"错误,但通过curl测试却完全正常。这个问题的定位过程充满了意外和惊喜,值得记录下来分享给大家。

问题现象

错误日志


2025-07-24 10:57:26 [downloadExecutorService-pool-0] ERROR o.p.x.s.b.DailyArticleDownloadService  下载失败: PMC12282765 - Remote host terminated the handshake2025-07-24 10:57:26 [downloadExecutorService-pool-0] WARN  o.p.x.s.b.DailyArticleDownloadService  🔄 下载失败: PMC12282765 (Remote host terminated the handshake), 第1次重试

但curl测试却成功


curl -x http://127.0.0.1:8118 -H "User-Agent: Mozilla/5.0..." -H "Accept: application/pdf" -H "cookie: cloudpmc-viewer-pow=TOKEN" -L -o /tmp/test.pdf "https://pmc.ncbi.nlm.nih.gov/articles/PMC12282765/pdf"# 下载成功,文件大小2.5MB

问题定位过程

第一步:网络连通性测试

首先确认基础网络连通性:


# 测试代理连通性curl -x http://127.0.0.1:8118 -I https://www.baidu.com# ✅ 成功# 测试PubMed APIcurl -x http://127.0.0.1:8118 -I "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pmc&mindate=2025/07/23&maxdate=2025/07/23&datetype=pdat&retmax=10&retmode=json"# ✅ 成功,返回405(正常,因为用了HEAD请求)# 测试PDF下载链接curl -x http://127.0.0.1:8118 -I "https://pmc.ncbi.nlm.nih.gov/articles/PMC12282765/pdf"# ❌ 返回403 Forbidden(需要token)

第二步:Token认证测试

发现需要token认证,测试带token的请求:


# 测试带token的PDF下载curl -x http://127.0.0.1:8118 -I -H "User-Agent: Mozilla/5.0..." -H "Accept: application/pdf" -H "cookie: cloudpmc-viewer-pow=TOKEN" "https://pmc.ncbi.nlm.nih.gov/articles/PMC12282765/pdf"# ✅ 返回301重定向# 跟随重定向curl -x http://127.0.0.1:8118 -I -H "User-Agent: Mozilla/5.0..." -H "Accept: application/pdf" -H "cookie: cloudpmc-viewer-pow=TOKEN" -L "https://pmc.ncbi.nlm.nih.gov/articles/PMC12282765/pdf"# ✅ 返回200,Content-Type: application/pdf

第三步:Java代码诊断

既然curl成功,问题可能在Java代码。创建诊断接口:


@RestController@RequestMapping("/diagnostic")public class DiagnosticController {@GetMapping("/comprehensive-test")public Map<String, Object> comprehensiveTest() {// 测试代理连通性、PubMed API、PDF SSL连接等}@GetMapping("/concurrent-test")public Map<String, Object> concurrentTest() {// 测试并发下载}}

诊断结果:所有测试都成功!


{"proxyTest": {"success": true, "statusCode": 200},"pubmedApiTest": {"success": true, "statusCode": 200},"pdfSslTest": {"success": true, "statusCode": 200},"pdfTokenTest": {"success": true, "statusCode": 200},"concurrentTest": {"success": true, "successCount": 2, "failureCount": 0}}

第四步:深入分析差异

这很奇怪!诊断接口成功,但实际下载任务失败。让我们对比:

  1. curl成功:使用token VwR3AGZmZwD4AwVhZwHjBGN0AvV:xDK5ppGzDRT8-HIxpqIeRiEKzy4pSs5CmywvDYwEPMf%2C3044

  2. Java诊断接口成功:并发测试也成功

  3. Java下载任务失败:使用token VwR3AGZmZwH4ZmDhZQp1Zwx0AlV:qf1SIS80cScsJoCpiL6WbY

第五步:检查实际下载代码

仔细检查DailyArticleDownloadService.java中的下载代码:


private SingleDownloadResult downloadSingleArticle(String pmcId, String token) {try {String baseUrl = "https://pmc.ncgibi.nlm.nih.gov";  // 等等...String pdfUrl = baseUrl + "/articles/PMC" + pmcId + "/pdf";// ...}}

发现问题了!

URL中的域名拼写错误:pmc.ncgibi.nlm.nih.gov 应该是 pmc.ncbi.nlm.nih.gov

问题根源

为什么会出现这种错误?

  1. 错误的域名解析ncgibi 可能解析到了某个服务器

  2. SSL握手失败:该服务器在SSL握手阶段就断开了连接

  3. 错误包装:Java的SSL库将连接中断包装成了"Remote host terminated the handshake"

为什么诊断接口成功?

诊断接口使用的是正确的URL,而实际下载任务使用的是错误的URL。

解决方案

修复URL拼写错误:


// 修复前String baseUrl = "https://pmc.ncgibi.nlm.nih.gov";// 修复后  String baseUrl = "https://pmc.ncbi.nlm.nih.gov";

经验总结

1. 不要被错误信息误导

"Remote host terminated the handshake"听起来像是SSL配置或网络问题,但实际上可能是URL错误。

2. 对比测试很重要

通过curl测试确认了网络和认证都正常,这帮助我们排除了很多可能的原因。

3. 代码审查要仔细

即使是简单的URL,也要仔细检查拼写。这种错误很容易被忽略。

4. 诊断工具的价值

创建诊断接口帮助我们快速定位问题,是很好的调试实践。

5. 日志分析要全面

不仅要看错误信息,还要对比不同场景下的日志,找出差异。

技术细节

为什么错误的域名会导致SSL握手失败?

  1. DNS解析:错误的域名可能解析到不存在的服务器或错误的IP

  2. 连接建立:TCP连接可能建立成功,但目标服务器不是预期的

  3. SSL握手:目标服务器可能不支持SSL或SSL配置不匹配

  4. 连接中断:服务器在握手过程中主动断开连接

为什么错误被包装成"Remote host terminated the handshake"?

这是Java SSL库的标准错误处理机制。当SSL握手过程中连接被远程服务器中断时,就会抛出这个错误。

预防措施

  1. 代码审查:建立代码审查流程,特别是URL和配置相关的代码

  2. 单元测试:为关键的网络请求编写单元测试

  3. 集成测试:定期运行完整的下载流程测试

  4. 监控告警:设置下载失败率的监控告警

  5. 文档记录:记录常见问题和解决方案

结语

这次Bug定位过程虽然最终发现是一个简单的拼写错误,但整个过程展示了系统化调试的重要性。通过逐步排除、对比测试、深入分析,我们最终找到了问题的根源。

这种"简单"的Bug往往最难发现,因为它们隐藏在复杂的系统架构和错误信息背后。但只要我们保持耐心,运用正确的调试方法,就一定能找到问题的真相。


这次调试经历让我深刻体会到:在软件开发中,最复杂的问题往往有最简单的解决方案,而最简单的错误往往最难发现。

http://www.dtcms.com/a/295886.html

相关文章:

  • 《C++ string 完全指南:string的模拟实现》
  • rust-枚举
  • 开源链动2+1模式AI智能名片S2B2C商城小程序的场景体验分析
  • HBase + PostgreSQL + ElasticSearch 联合查询方案
  • vue3 el-table 列数据合计
  • MongoDB 副本集搭建与 Monstache 实时同步 Elasticsearch 全流程教程
  • AI开放课堂:钉钉MCP开发实战
  • 【DBeaver 安装 MongoDB 插件】
  • 推荐系统如何开发
  • Python —— 真题九
  • web:js函数的prototype(原型对象)属性
  • RabbitMQ简述
  • 前端笔记:同源策略、跨域问题
  • 重绘(Repaint)与重排(Reflow)
  • 【ECharts✨】解决Vue 中 v-show 导致组件 ECharts 样式异常问题
  • 简单Proxy使用
  • 【Newman+Jenkins】实施接口自动化测试
  • Python 使用环境下编译 FFmpeg 及 PyAV 源码(英特尔篇)
  • AIRIOT智慧选煤厂管理解决方案
  • HTTP性能优化实战:从协议到工具的全面加速指南
  • 【unity游戏开发入门到精通——组件篇】unity的粒子系统力场 (Particle System Force Field)实现如旋风、吸引力、风吹效果等
  • OpenCV(03)插值方法,边缘填充,透视变换,水印制作,噪点消除
  • Python中浅拷贝和深拷贝的理解
  • 力扣 hot100 Day54
  • mvn dependency:tree 使用详解?
  • Leetcode 07 java
  • 赋能决策与创新的数据引擎:数据分析平台的价值与未来
  • b-up:Enzo_mi:Transformer DETR系列
  • 10_Spring Boot 中的 @Scheduled 注解是单线程还是多线程?同步还是异步?
  • 基于深度学习的肺癌肿瘤细胞图像识别与分类系统