【发布时间】:2017-05-08 16:10:51
【问题描述】:
我正在与一个通过 HTTPS 接受 POST 请求并以 XML 响应的 API 进行交互。远程服务器生成数据需要很长时间,这意味着我的本地客户端在发送其 POST 请求和接收响应之间等待几分钟。有时响应会按预期返回,但有时 Ruby (2.3.1p112) 会在长时间暂停后引发以下异常:
Exception occurred: Errno::ECONNRESET - Connection reset by peer
/usr/share/ruby/openssl/buffering.rb:178:in `sysread_nonblock'
/usr/share/ruby/openssl/buffering.rb:178:in `read_nonblock'
/usr/share/ruby/net/protocol.rb:154:in `rbuf_fill'
/usr/share/ruby/net/protocol.rb:136:in `readuntil'
/usr/share/ruby/net/protocol.rb:146:in `readline'
/usr/share/ruby/net/http/response.rb:40:in `read_status_line'
/usr/share/ruby/net/http/response.rb:29:in `read_new'
/usr/share/ruby/net/http.rb:1437:in `block in transport_request'
/usr/share/ruby/net/http.rb:1434:in `catch'
/usr/share/ruby/net/http.rb:1434:in `transport_request'
/usr/share/ruby/net/http.rb:1407:in `request'
出于好奇,我尝试使用 cURL 发出相同的请求,发现每次都收到响应,从未遇到远程主机发起的连接重置。 cURL 和 Ruby 请求在同一台机器上运行,所以我认为这不是低级系统或网络问题。为了找出一些不同,我运行了 Wireshark 并查看了请求期间来回发送的数据包。
首先使用 Ruby:
1302978 3988.123708 [local-ip] → [remote-ip] SSL 286 Client Hello
1302981 3988.189299 [remote-ip] → [local-ip] TLSv1.2 463 Server Hello, Certificate
1302982 3988.189388 [local-ip] → [remote-ip] TCP 54 55265 → 443 [ACK] Seq=233 Ack=1361 Win=65535 Len=0
1302983 3988.189389 [local-ip] → [remote-ip] TCP 54 55265 → 443 [ACK] Seq=233 Ack=2721 Win=65535 Len=0
1302984 3988.189389 [local-ip] → [remote-ip] TCP 54 55265 → 443 [ACK] Seq=233 Ack=3130 Win=65535 Len=0
1302985 3988.194978 [remote-ip] → [local-ip] TLSv1.2 396 Server Key Exchange, Server Hello Done
1302986 3988.195047 [local-ip] → [remote-ip] TCP 54 55265 → 443 [ACK] Seq=233 Ack=3472 Win=65535 Len=0
1302987 3988.195812 [local-ip] → [remote-ip] TLSv1.2 180 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
1302988 3988.257643 [remote-ip] → [local-ip] TCP 60 443 → 55265 [ACK] Seq=3472 Ack=359 Win=35098 Len=0
1302989 3988.272131 [remote-ip] → [local-ip] TLSv1.2 105 Change Cipher Spec, Encrypted Handshake Message
1302990 3988.272200 [local-ip] → [remote-ip] TCP 54 55265 → 443 [ACK] Seq=359 Ack=3523 Win=65535 Len=0
1302994 3988.339450 [remote-ip] → [local-ip] TCP 60 443 → 55265 [ACK] Seq=3523 Ack=945 Win=34512 Len=0
1302995 3988.339455 [remote-ip] → [local-ip] TCP 60 443 → 55265 [ACK] Seq=3523 Ack=1206 Win=34251 Len=0
1406186 4376.828078 [remote-ip] → [local-ip] TCP 54 443 → 55265 [RST, ACK] Seq=3523 Ack=1206 Win=9300 Len=0
然后用 cURL:
51468 268.062527 [local-ip] → [remote-ip] SSL 292 Client Hello
51472 268.125416 [remote-ip] → [local-ip] TLSv1.2 463 Server Hello, Certificate
51473 268.125530 [local-ip] → [remote-ip] TCP 54 53819 → 443 [ACK] Seq=239 Ack=1361 Win=65535 Len=0
51474 268.125531 [local-ip] → [remote-ip] TCP 54 53819 → 443 [ACK] Seq=239 Ack=2721 Win=65535 Len=0
51475 268.125531 [local-ip] → [remote-ip] TCP 54 53819 → 443 [ACK] Seq=239 Ack=3130 Win=65535 Len=0
51476 268.132509 [remote-ip] → [local-ip] TLSv1.2 396 Server Key Exchange, Server Hello Done
51477 268.132604 [local-ip] → [remote-ip] TCP 54 53819 → 443 [ACK] Seq=239 Ack=3472 Win=65535 Len=0
51479 268.158620 [local-ip] → [remote-ip] TLSv1.2 129 Client Key Exchange
51481 268.220146 [remote-ip] → [local-ip] TCP 60 443 → 53819 [ACK] Seq=3472 Ack=314 Win=35137 Len=0
51482 268.220216 [local-ip] → [remote-ip] TLSv1.2 105 Change Cipher Spec, Encrypted Handshake Message
51483 268.281636 [remote-ip] → [local-ip] TCP 60 443 → 53819 [ACK] Seq=3472 Ack=365 Win=35086 Len=0
51484 268.281642 [remote-ip] → [local-ip] TLSv1.2 105 Change Cipher Spec, Encrypted Handshake Message
51485 268.281718 [local-ip] → [remote-ip] TCP 54 53819 → 443 [ACK] Seq=365 Ack=3523 Win=65535 Len=0
51487 268.344020 [remote-ip] → [local-ip] TCP 60 443 → 53819 [ACK] Seq=3523 Ack=770 Win=34681 Len=0
62427 328.950531 [local-ip] → [remote-ip] TCP 54 [TCP Keep-Alive] 53819 → 443 [ACK] Seq=769 Ack=3523 Win=65535 Len=0
62435 329.012004 [remote-ip] → [local-ip] TCP 60 [TCP Window Update] 443 → 53819 [ACK] Seq=3523 Ack=770 Win=65535 Len=0
72644 389.584563 [local-ip] → [remote-ip] TCP 54 [TCP Keep-Alive] 53819 → 443 [ACK] Seq=769 Ack=3523 Win=65535 Len=0
72647 389.647037 [remote-ip] → [local-ip] TCP 60 [TCP Keep-Alive ACK] 443 → 53819 [ACK] Seq=3523 Ack=770 Win=65535 Len=0
我发现的相关区别是 cURL 在等待响应时每分钟发送一个 TCP Keep-Alive 数据包,让服务器知道它仍然处于活动状态并希望保持其连接打开。 Ruby Net::HTTP 没有发送这些数据包,并最终从远程主机获得 TCP RST(重置),这会触发 Errno::ECONNRESET 异常。
所以我的问题是:有没有办法配置 Net::HTTP 来发送那些 TCP Keep-Alive 数据包并保持我的连接打开?谢谢!
编辑:我可以通过切换到 Typhoeus(包含 cURL)并将选项 tcp_keepalive: true 传递给我的请求来解决这个问题,这是 tells cURL to send keepalive probes 的标志。
如果有人知道是否有可能从 Net::HTTP 获得相同的行为,我将保留这个问题。
【问题讨论】:
标签: ruby ssl networking tcp