解决:Error: connect ETIMEDOUT at TCPConnectWrap.afterConnect

5,116 阅读2分钟

问题背景

自从 Node.js 8.0 开始,http server 增加了一个默认配置 keepAliveTimeout = 5000 ,它会自动销毁超过 5 秒的空闲连接。 或者在源码里最后一行也可以找到 node/_http_agent.js at main · nodejs/node

通过 node-modules/agentkeepalive#58 的示例代码,非常容易重现出服务端 Keep Alive 超时的效果,就是客户端复用「Keep Alive」 的 socket connection 会立刻触发 ECONNRESET 异常,因为此 socket 已经被服务端认为空闲超时强制断开了。

err stringify: {
    "message":"connect ETIMEDOUT 39.96.33.114:443","name":"Error",
    "stack":"Error: connect ETIMEDOUT 39.96.33.114:443\n at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1148:16)",
    "config": {
        "url":"xxxx",
        "method":"post",
        "data":"token=dJC1X1O9z7TsmARBoJxSSUY1TlWxKigRHw~~&templateId=ce0adbff97a2b6361nY~&needData=1",
    "headers":{
        "Accept":"application/json, text/plain, */*",
        "Content-Type":"application/x-www-form-urlencoded",
        "User-Agent":"axios/0.21.4",
        "Content-Length":325
     },
     "transformRequest":[null],
     "transformResponse":[null],
     "timeout":0,
     "xsrfCookieName":"XSRF-TOKEN",
     "xsrfHeaderName":"X-XSRF-TOKEN",
     "maxContentLength":-1,
     "maxBodyLength":-1,
     "transitional":{
         "silentJSONParsing":true,
         "forcedJSONParsing":true,
         "clarifyTimeoutError":false}
      },
      "code":"ETIMEDOUT"
};

err name: Error err message: connect ETIMEDOUT 39.96.33.114:443 
err stack: Error: connect ETIMEDOUT 39.96.33.114:443 at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1148:16)

ECONNRESET VS ETIMEDOUT

这里注意区分下 ECONNRESET 与 ETIMEDOUT 的区别

ECONNRESET 为读取超时,当服务器太慢无法正常响应时就会发生 {"code":"ECONNRESET"} 错误,例如上面介绍的 socket hang up 例子。

ETIMEDOUT 为链接超时,是指的在客户端与远程服务器建立链接发生的超时,

解决 ECONNRESET 错误的话, 配置 **设置 http server socket 超时时间** 解决 ETIMEDOUT , 配置 Agent 里的timeout 参数

一种解决方式:

const axios = require('axios');                                             
const http = require('http');                                        
const https = require('https');                                      
module.exports = axios.create({                                      
     //60 sec timeout                            
     timeout: 60000,  
     //keepAlive pools and reuses TCP connections, so it's faster 
     httpAgent: new http.Agent({ keepAlive: true }), 
     httpsAgent: new https.Agent({ keepAlive: true }),    
     //follow up to 10 HTTP 3xx redirects 
     maxRedirects: 10,                
     //cap the maximum content length we'll accept to 50MBs, just in case 
     maxContentLength: 50 * 1000 * 1000    
});

另外解决方式:

//  安装 agentkeepalive 包
npm install agentkeepalive --save
// 配置: axios.js
module.exports = axios.create({
    // 60 sec timeout
    timeout: 1 * 60 * 1000,

    // keepAlive pools and reuses TCP connections, so it's faster
    httpAgent: new Agent({
        maxSockets: 100,
        maxFreeSockets: 10,
        timeout: 1 * 60 * 1000,
        freeSocketTimeout: 1 * 60 * 1000,
        keepAlive: true
    }),
    httpsAgent: new Agent.HttpsAgent({
        maxSockets: 100,
        maxFreeSockets: 10,
        timeout: 2 * 60 * 1000,
        freeSocketTimeout: 1 * 60 * 1000,
        keepAlive: true
    }),

    // follow up to 10 HTTP 3xx redirects
    maxRedirects: 10,

    // cap the maximum content length we'll accept to 50MBs, just in case
    maxContentLength: 50 * 1000 * 1000
});

最后采用 agentkeepalive, 理由:

What's different from original http.Agent?

  • keepAlive=true by default
  • Disable Nagle's algorithm: socket.setNoDelay(true)
  • Add free socket timeout: avoid long time inactivity socket leak in the free-sockets queue.
  • Add active socket timeout: avoid long time inactivity socket leak in the active-sockets queue.
  • TTL for active socket.

参考文章:

  1. node.js - Error: connect ETIMEDOUT at TCPConnectWrap.afterConnect [as oncomplete]
  2. Socket hang up 是什么?什么情况下会发生?
  3. Good default configuration for axios in node.js
  4. node-modules/agentkeepalive: Support keepalive http agent.
  5. javascript - How to handle ETIMEDOUT error?