Ruby Net::HTTP下载文件异常超时问题的排查与解决
解决Ruby下载文件时的IO::TimeoutError及SSL证书不匹配问题
问题描述
使用Ruby下载https://evs.nci.nih.gov/ftp1/CDISC/ReadMe.txt时,抛出IO::TimeoutError,提示无法建立TCP连接。进一步测试发现:
TCPSocket直接连接域名时超时- 解析域名得到IPv6和IPv4地址,直接连接IPv4地址会触发SSL证书不匹配错误,但
curl可正常下载该文件
根因分析
问题出在Net::HTTP#connect方法中使用的Timeout.timeout存在设计缺陷,导致在同时存在IPv6和IPv4解析结果时,连接逻辑出现超时。而curl会自动尝试合适的地址并正确处理SSL验证,因此可以正常下载。
解决方案
方案1:指定IPv4地址并处理SSL证书验证
先通过域名解析工具(如dig或nslookup)获取目标域名的IPv4地址,然后在代码中指定该地址,并设置hostname用于SSL证书验证,解决证书不匹配问题:
require 'net/http' require 'openssl' target_url = URI('https://evs.nci.nih.gov/ftp1/CDISC/ReadMe.txt') # 替换为实际解析得到的evs.nci.nih.gov的IPv4地址 ipv4_address = '198.245.60.189' http = Net::HTTP.new(ipv4_address, target_url.port) http.use_ssl = true # 启用SSL证书验证,并指定主机名用于证书匹配 http.verify_mode = OpenSSL::SSL::VERIFY_PEER http.hostname = target_url.host # 发起请求并保存文件 response = http.get(target_url.path) File.write('ReadMe.txt', response.body)
方案2:替换Net::HTTP的连接逻辑,使用Socket.tcp
重写Net::HTTP#connect方法,用Socket.tcp替代默认的Timeout.timeout逻辑,避免超时缺陷:
require 'net/http' require 'openssl' target_url = URI('https://evs.nci.nih.gov/ftp1/CDISC/ReadMe.txt') http = Net::HTTP.new(target_url.host, target_url.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER http.open_timeout = 10 # 重写connect方法,使用Socket.tcp处理连接 def http.connect @socket = Net::BufferedIO.new( Socket.tcp(@address, @port, nil, nil, connect_timeout: @open_timeout) ) if use_ssl? ssl_ctx = @ssl_context ? @ssl_context.dup : OpenSSL::SSL::SSLContext.new ssl_ctx.hostname = @address @socket = OpenSSL::SSL::SSLSocket.new(@socket, ssl_ctx) @socket.sync_close = true @socket.connect end end response = http.get(target_url.path) File.write('ReadMe.txt', response.body)
内容的提问来源于stack exchange,提问作者Thierry Lambert
相关产品推荐
相关产品推荐

