Caver-js 1.5.0 Websocket reconnect 에러 관련 문의

안녕하세요. Caver Websocket관련 도움이 필요합니다.
현재 토큰 컨트랙트에서 발생한 입출금 관련 이벤트들을 조회할 목적으로 웹소켓을 사용하고 있습니다.

어플리케이션 서버를 동작시킨 바로 직후에는 통신도 매우 잘되고 이벤트들 받아오는 것도 이상없이 동작하는데 문제는, 아무 액션 없이 2분 정도 뒤에 다시 호출하면 에러가 발생한다는 점입니다.

첨부한 사진은 에러가 나는 부분을 캡쳐한 부분인데요, 콘솔에 출력해서 확인해본 에러 메시지 또한 아래 기재해드립니다.

connection not open on send()
    Error: connection not open. try reconnect..
        at WebsocketProvider.send (/home/webdev/cryptobric-api/node_modules/caver-js/packages/caver-core-requestmanager/caver-providers-ws/src/index.js:258:18)
        at /home/webdev/cryptobric-api/node_modules/caver-js/packages/caver-core-requestmanager/src/index.js:132:64
        at RequestManager.send (/home/webdev/cryptobric-api/node_modules/caver-js/packages/caver-core-requestmanager/src/index.js:125:58)
        at /home/webdev/cryptobric-api/node_modules/caver-js/packages/caver-core-method/src/index.js:420:34
        at /home/webdev/cryptobric-api/node_modules/caver-js/packages/caver-core-method/src/index.js:435:9
        at Contract.getPastEvents (/home/webdev/cryptobric-api/node_modules/caver-js/packages/caver-contract/src/index.js:829:12)
        at /home/webdev/cryptobric-api/routers/history.js:91:13
        at Layer.handle [as handle_request] (/home/webdev/cryptobric-api/node_modules/express/lib/router/layer.js:95:5)
        at next (/home/webdev/cryptobric-api/node_modules/express/lib/router/route.js:137:13)
        at Route.dispatch (/home/webdev/cryptobric-api/node_modules/express/lib/router/route.js:112:3)

at WebsocketProvider.send (/home/webdev/cryptobric-api/node_modules/caver-js/packages/caver-core-requestmanager/caver-providers-ws/src/index.js:258:18) 에러가 짚어준 코드를 살펴보았는데... 아무래도 reconnect 부분이 제대로 동작하고 있지 않은 것 같습니다.

참고 사항으로 해당 reconnect 코드 부분의 이전과 이후에 다음과 같이 콘솔 로그로 출력하는 부분을 넣어 디버깅 한 결과도 기재해드립니다.

WebsocketProvider.prototype.send = function(payload, callback) {
    const _this = this

    if (this.connection.readyState === this.connection.CONNECTING) {
        setTimeout(function() {
            _this.send(payload, callback)
        }, 10)
        return
    }

    // try reconnect, when connection is gone
    // if(!this.connection.writable)
    //     this.connection.connect({url: this.url});
    if (this.connection.readyState !== this.connection.OPEN) {
        console.error('connection not open on send()')
        console.log(typeof this.connection.onerror);
        if (typeof this.connection.onerror === 'function') {
            this.connection.onerror(new Error('connection not open'))
        } else {
            console.error('no error callback')
        }
        // reconnect
        console.log(this.connection);
        console.log('try reconnect')
        this.connection = this.reconnect()
        console.log('reconnect finished')
        console.log(this.connection);
        callback(new Error('connection not open. try reconnect..'))
        return
    }

    this.connection.send(JSON.stringify(payload))
    this._addResponseCallback(payload, callback)
}

reconnect 전에 출력한 웹소켓 내용은 아래와 같습니다.

W3CWebSocket {
  _listeners: {},
  addEventListener: [Function: _addEventListener],
  removeEventListener: [Function: _removeEventListener],
  dispatchEvent: [Function: _dispatchEvent],
  _url: 'wss://api.baobab.klaytn.net:8652',
  _readyState: 3,
  _protocol: undefined,
  _extensions: [],
  _bufferedAmount: 0,
  _binaryType: 'arraybuffer',
  _connection: WebSocketConnection {
    _debug: [Function: debug] {
      namespace: 'websocket:connection',
      enabled: false,
      useColors: true,
      color: 2,
      inspectOpts: {},
      printOutput: [Function (anonymous)]
    },
    _events: [Object: null prototype] {},
    _eventsCount: 0,
    _maxListeners: undefined,
    _pingListenerCount: 0,
    config: {
      maxReceivedFrameSize: 1048576,
      maxReceivedMessageSize: 8388608,
      fragmentOutgoingMessages: true,
      fragmentationThreshold: 16384,
      webSocketVersion: 13,
      assembleFragments: true,
      disableNagleAlgorithm: true,
      closeTimeout: 5000,
      tlsOptions: {}
    },
    socket: TLSSocket {
      _tlsOptions: [Object],
      _secureEstablished: true,
      _securePending: false,
      _newSessionPending: false,
      _controlReleased: true,
      _SNICallback: null,
      servername: 'api.baobab.klaytn.net',
      alpnProtocol: false,
      authorized: true,
      authorizationError: null,
      encrypted: true,
      _events: [Object: null prototype],
      _eventsCount: 11,
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'api.baobab.klaytn.net',
      _readableState: [ReadableState],
      readable: false,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: false,
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: undefined,
      _server: null,
      ssl: null,
      _requestCert: true,
      _rejectUnauthorized: true,
      parser: null,
      _httpMessage: null,
      _peername: [Object],
      timeout: 0,
      write: [Function: writeAfterFIN],
      [Symbol(res)]: null,
      [Symbol(asyncId)]: 6,
      [Symbol(kHandle)]: null,
      [Symbol(kSetNoDelay)]: true,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 391148,
      [Symbol(kBytesWritten)]: 18521,
      [Symbol(connect-options)]: [Object]
    },
    protocol: undefined,
    extensions: [],
    remoteAddress: '54.180.51.111',
    closeReasonCode: 1006,
    closeDescription: 'Connection dropped by remote peer.',
    closeEventEmitted: true,
    maskOutgoingPackets: true,
    maskBytes: <Buffer d9 87 f0 3a>,
    frameHeader: <Buffer 81 7e 08 38 01 00 00 00 40 b7>,
    bufferList: BufferList {
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: undefined,
      encoding: undefined,
      length: [Getter],
      write: [Function (anonymous)],
      end: [Function (anonymous)],
      push: [Function (anonymous)],
      forEach: [Function (anonymous)],
      join: [Function (anonymous)],
      joinInto: [Function (anonymous)],
      advance: [Function (anonymous)],
      take: [Function (anonymous)],
      toString: [Function (anonymous)],
      [Symbol(kCapture)]: false
    },
    currentFrame: WebSocketFrame {
      maskBytes: <Buffer d9 87 f0 3a>,
      frameHeader: <Buffer 81 7e 08 38 01 00 00 00 40 b7>,
      config: [Object],
      maxReceivedFrameSize: 1048576,
      protocolError: false,
      frameTooLarge: false,
      invalidCloseFrameLength: false,
      parseState: 1,
      closeStatus: -1
    },
    fragmentationSize: 0,
    frameQueue: [],
    connected: false,
    state: 'closed',
    waitingForCloseResponse: false,
    receivedEnd: true,
    closeTimeout: 5000,
    assembleFragments: true,
    maxReceivedMessageSize: 8388608,
    outputBufferFull: false,
    inputPaused: false,
    receivedDataHandler: [Function: bound ],
    _closeTimerHandler: [Function: bound ],
    webSocketVersion: 13,
    socketHadError: false,
    [Symbol(kCapture)]: false
  },
  _client: WebSocketClient {
    _events: [Object: null prototype] {},
    _eventsCount: 0,
    _maxListeners: undefined,
    config: {
      maxReceivedFrameSize: 1048576,
      maxReceivedMessageSize: 8388608,
      fragmentOutgoingMessages: true,
      fragmentationThreshold: 16384,
      webSocketVersion: 13,
      assembleFragments: true,
      disableNagleAlgorithm: true,
      closeTimeout: 5000,
      tlsOptions: {}
    },
    _req: null,
    protocols: [],
    origin: undefined,
    url: Url {
      protocol: 'wss:',
      slashes: true,
      auth: null,
      host: 'api.baobab.klaytn.net:8652',
      port: '8652',
      hostname: 'api.baobab.klaytn.net',
      hash: null,
      search: null,
      query: null,
      pathname: '/',
      path: '/',
      href: 'wss://api.baobab.klaytn.net:8652/'
    },
    secure: true,
    base64nonce: 'RgF/2pU93/ttTqQtxRvhSw==',
    socket: TLSSocket {
      _tlsOptions: [Object],
      _secureEstablished: true,
      _securePending: false,
      _newSessionPending: false,
      _controlReleased: true,
      _SNICallback: null,
      servername: 'api.baobab.klaytn.net',
      alpnProtocol: false,
      authorized: true,
      authorizationError: null,
      encrypted: true,
      _events: [Object: null prototype],
      _eventsCount: 11,
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: 'api.baobab.klaytn.net',
      _readableState: [ReadableState],
      readable: false,
      _maxListeners: undefined,
      _writableState: [WritableState],
      writable: false,
      allowHalfOpen: false,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: undefined,
      _server: null,
      ssl: null,
      _requestCert: true,
      _rejectUnauthorized: true,
      parser: null,
      _httpMessage: null,
      _peername: [Object],
      timeout: 0,
      write: [Function: writeAfterFIN],
      [Symbol(res)]: null,
      [Symbol(asyncId)]: 6,
      [Symbol(kHandle)]: null,
      [Symbol(kSetNoDelay)]: true,
      [Symbol(lastWriteQueueSize)]: 0,
      [Symbol(timeout)]: null,
      [Symbol(kBuffer)]: null,
      [Symbol(kBufferCb)]: null,
      [Symbol(kBufferGen)]: null,
      [Symbol(kCapture)]: false,
      [Symbol(kBytesRead)]: 391148,
      [Symbol(kBytesWritten)]: 18521,
      [Symbol(connect-options)]: [Object]
    },
    response: IncomingMessage {
      _readableState: [ReadableState],
      readable: true,
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: undefined,
      socket: [TLSSocket],
      httpVersionMajor: 1,
      httpVersionMinor: 1,
      httpVersion: '1.1',
      complete: true,
      headers: [Object],
      rawHeaders: [Array],
      trailers: {},
      rawTrailers: [],
      aborted: false,
      upgrade: true,
      url: '',
      method: null,
      statusCode: 101,
      statusMessage: 'Switching Protocols',
      client: [TLSSocket],
      _consuming: false,
      _dumped: false,
      [Symbol(kCapture)]: false
    },
    firstDataChunk: null,
    [Symbol(kCapture)]: false
  },
  onerror: [Function (anonymous)],
  onclose: [Function (anonymous)],
  onmessage: [Function (anonymous)]
}

reconnect 이후 출력한 웹소켓 내용은 아래와 같습니다.

W3CWebSocket {
  _listeners: {},
  addEventListener: [Function: _addEventListener],
  removeEventListener: [Function: _removeEventListener],
  dispatchEvent: [Function: _dispatchEvent],
  _url: 'wss://api.baobab.klaytn.net:8652',
  _readyState: 0,
  _protocol: undefined,
  _extensions: '',
  _bufferedAmount: 0,
  _binaryType: 'arraybuffer',
  _connection: undefined,
  _client: WebSocketClient {
    _events: [Object: null prototype] {
      connect: [Function (anonymous)],
      connectFailed: [Function (anonymous)]
    },
    _eventsCount: 2,
    _maxListeners: undefined,
    config: {
      maxReceivedFrameSize: 1048576,
      maxReceivedMessageSize: 8388608,
      fragmentOutgoingMessages: true,
      fragmentationThreshold: 16384,
      webSocketVersion: 13,
      assembleFragments: true,
      disableNagleAlgorithm: true,
      closeTimeout: 5000,
      tlsOptions: {}
    },
    _req: ClientRequest {
      _events: [Object: null prototype],
      _eventsCount: 4,
      _maxListeners: undefined,
      outputData: [Array],
      outputSize: 165,
      writable: true,
      _last: true,
      chunkedEncoding: false,
      shouldKeepAlive: true,
      useChunkedEncodingByDefault: false,
      sendDate: false,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      _contentLength: 0,
      _hasBody: true,
      _trailer: '',
      finished: true,
      _headerSent: true,
      socket: null,
      _header: 'GET / HTTP/1.1\r\n' +
        'Upgrade: websocket\r\n' +
        'Connection: Upgrade\r\n' +
        'Sec-WebSocket-Version: 13\r\n' +
        'Sec-WebSocket-Key: 1VrcNXMmrND6WRONckZZXQ==\r\n' +
        'Host: api.baobab.klaytn.net:8652\r\n' +
        '\r\n',
      _onPendingData: [Function: noopPendingOutput],
      agent: [Agent],
      socketPath: undefined,
      method: 'GET',
      maxHeaderSize: undefined,
      insecureHTTPParser: undefined,
      path: '/',
      _ended: false,
      res: null,
      aborted: false,
      timeoutCb: null,
      upgradeOrConnect: false,
      parser: null,
      maxHeadersCount: null,
      reusedSocket: false,
      [Symbol(kCapture)]: false,
      [Symbol(kNeedDrain)]: false,
      [Symbol(corked)]: 0,
      [Symbol(kOutHeaders)]: [Object: null prototype]
    },
    protocols: [],
    origin: undefined,
    url: Url {
      protocol: 'wss:',
      slashes: true,
      auth: null,
      host: 'api.baobab.klaytn.net:8652',
      port: '8652',
      hostname: 'api.baobab.klaytn.net',
      hash: null,
      search: null,
      query: null,
      pathname: '/',
      path: '/',
      href: 'wss://api.baobab.klaytn.net:8652/'
    },
    secure: true,
    base64nonce: '1VrcNXMmrND6WRONckZZXQ==',
    [Symbol(kCapture)]: false
  }
}

긴 내용의 에러임에도 불구하고 끝까지 읽어주셔서 감사합니다.
문의 내용을 요약드리자면 “Websocket의 reconnect 부분이 불완전한 것 같은데 어떻게 보완할 수 있을까요?” 입니다. ㅠㅠ 도와주십쇼.

아래는 web3.js에서 저랑 비슷한 문제를 겪은 누군가 올린 스택오버플로 링크입니다 :frowning: 많이 바쁘시겠지만 살펴봐주시면 감사하겠습니다 ㅠ_ㅠ

안녕하세요 :slight_smile: reconnect와 관련하여서 caver-js 구현이 조금 변경되어야 하는 부분이 있어서 진행 중에 있습니다.

해당 구현이 반영되고 rc버전이 배포되는 대로 답변 다시 드리겠습니다.
조금만 기다려 주세요 ^^!

1 Like

답변 주셔서 감사합니다. :slight_smile:

넵! 말씀해주십시오.
클레이튼 항상 응원합니다.

2 Likes

기다려 주셔서 감사합니다.

caver-js v1.5.1-rc.2 버전이 배포되었습니다. caver-js v1.5.1-rc.2 버전부터는 reconnect에 대한 옵션을 지정할 수 있습니다. 아래와 같이 1.5.1-rc.2 버전을 설치하여 사용해 주시기 바랍니다.

npm install caver-js@v1.5.1-rc.2

일단 질문해 주신 문제는 특정 시간 동안 통신을 안하는 경우 remote peer에서 connection을 끊는데, 이 경우에 다시 reconnect를 하지 못해서 발생한 문제였습니다.

그래서 connection이 끊기는 경우 다시 reconnect를 시도하기 위해서는 아래와 같이 reconnect 옵션을 따로 지정해 주시면 됩니다.

    const ws = new Caver.providers.WebsocketProvider('wss://api.baobab.klaytn.net:8652/', { reconnect: { auto: true } })
    const caver = new Caver(ws)

위와 같이 websocket provider를 생성하실 때에 reconnect 옵션을 지정하여서 해보시고, 만약에 동작이 정상적으로 하지 않는 경우 다시 말씀해 주세요 :slight_smile: 감사합니다!

3 Likes

넵 적용해보고 말씀드릴게요. 도움 감사합니다 :wink:

1 Like

아 제가 답변을 안했었군요.

관련 웹소켓 문제는 해결되었습니다 :slight_smile:
패치 적용하니 바로 되었어요.

도움주신 @Jamie 님 고맙습니다… 멋지신 분 :+1:

2 Likes