eggjs / egg

🥚 Born to build better enterprise frameworks and apps with Node.js & Koa
https://eggjs.org
MIT License
18.87k stars 1.81k forks source link

curl中通过 stream 传递请求数据后超时error捕捉不到 #3368

Closed jiang43605 closed 5 years ago

jiang43605 commented 5 years ago

What happens?

curl设置streaming为true后,如果请求的接口超时,无法捕捉超时错误且浏览器会卡死无限等待

    //  http://127.0.0.1/home/proxy
    public async proxy() {
        await new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('ok');
            }, 6000);
        });

        this.ctx.body = 'ok';
    }

    public async test() {
        let result: any;
        try {
            result = await this.ctx.curl('http://127.0.0.1/home/proxy', {
                stream: this.ctx.request.req,
                streaming: true,
                timeout: [3000, 5000]
            });

            this.ctx.body = result.res;
        } catch (error) {
            await sendToWormhole(result.res);
            this.ctx.body = 'error';
        }
    }

相关环境信息

killagu commented 5 years ago

streaming Boolean - let you get the res object when request connected, default false. alias customResponse

Please delete streaming: true, in request options.

egg-bot commented 5 years ago

Hello @jiang43605, we use GitHub issues to trace bugs or discuss plans of Egg.js. So, please don't ask usage questions here. You can try to ask questions on Stack Overflow or CNode.

jiang43605 commented 5 years ago

为什么要删除streaming: true?我就是要这个功能,你们文档也是这么写的:https://eggjs.org/zh-cn/core/httpclient.html#streaming-boolean

killagu commented 5 years ago

If you set streaming to true, you will get the raw response object, so you should handle the timeout self.

cc @fengmk2 Is it a bug or feature?

jiang43605 commented 5 years ago

so you should handle the timeout self.

怎么做?上面我提供的代码中,如果超时,将得不到任何返回对象。最外层try...catch...也无法捕捉

jiang43605 commented 5 years ago
...
  function startConnectTimer() {
    debug('Connect timer ticking, timeout: %d', connectTimeout);
    connectTimer = setTimeout(function () {
      connectTimer = null;
      if (statusCode === -1) {
        statusCode = -2;
      }
      var msg = 'Connect timeout for ' + connectTimeout + 'ms';
      var errorName = 'ConnectionTimeoutError';
      if (!req.socket) {
        errorName = 'SocketAssignTimeoutError';
        msg += ', working sockets is full';
      }
      __err = new Error(msg);
      __err.name = errorName;
      __err.requestId = reqId;
      debug('ConnectTimeout: Request#%d %s %s: %s, connected: %s', reqId, url, __err.name, msg, connected);
      // 超时后处理程序
      abortRequest();
    }, connectTimeout);
  }

  function startResposneTimer() {
    debug('Response timer ticking, timeout: %d', responseTimeout);
    responseTimer = setTimeout(function () {
      responseTimer = null;
      var msg = 'Response timeout for ' + responseTimeout + 'ms';
      var errorName = 'ResponseTimeoutError';
      __err = new Error(msg);
      __err.name = errorName;
      __err.requestId = reqId;
      debug('ResponseTimeout: Request#%d %s %s: %s, connected: %s', reqId, url, __err.name, msg, connected);
      // 超时后处理程序
      abortRequest();
    }, responseTimeout);
  }
...

翻了下源码,发现超时后会交由abortRequest处理,abortRequest代码如下:

  function abortRequest() {
    if (isRequestAborted) {
      return;
    }
    isRequestAborted = true;

    debug('Request#%d %s abort, connected: %s', reqId, url, connected);
    // it wont case error event when req haven't been assigned a socket yet.
    if (!req.socket) {
      __err.noSocket = true;
      done(__err);
    }
    // 推测问题所在
    // 最后未再调用done(),导致promise处于挂起状态,页面一直处于等待
    req.abort();
  }
dead-horse commented 5 years ago

确认一下使用的 urllib 版本,应该已经在 2.31.3 版本之后已修复。 https://github.com/node-modules/urllib/pull/305

dead-horse commented 5 years ago

是这样的场景么? https://github.com/node-modules/urllib/pull/307/files

popomore commented 5 years ago

好像有点差别,streaming 在 response 就直接返回了,没看怎么处理 timeout

dead-horse commented 5 years ago

感觉要模拟一个测试用例先返回 header 再返回 body 的场景

dead-horse commented 5 years ago

好像有点差别,streaming 在 response 就直接返回了,没看怎么处理 timeout

额,不对。如果是 streaming 的参数,timeout 只处理到 header 响应,如果 header 已经返回了,回调函数就已经拿到了 stream 了,urllib 的 timeout 不会再触发的。

popomore commented 5 years ago

嗯,是这个意思

fengmk2 commented 5 years ago

await sendToWormhole(result.res); 不应该写的,超时不会有 result.res。

jiang43605 commented 5 years ago

@fengmk2 嗯,这个写不写跟这个问题确实无关 @dead-horse 升级后问题解决,谢谢!

14glwu commented 5 years ago

同样遇到这个问题,关键是egg版本还是老的版本,无法升级

dead-horse commented 5 years ago

egg 是老版本无所谓,依赖的 urllib 升级就可以了,别锁死版本。

14glwu commented 5 years ago

egg 是老版本无所谓,依赖的 urllib 升级就可以了,别锁死版本。

非常感谢,升级了urllib ,成功的解决了问题,服务不再不响应了,能够返回错误信息给前端了。