체인의정석

ethers & websocket 참고 예제 본문

개발/backend

ethers & websocket 참고 예제

체인의정석 2023. 11. 27. 12:07
728x90
반응형

https://github.com/ethers-io/ethers.js/issues/1053

 

WebSocketProvider handle ws close and reconnect · Issue #1053 · ethers-io/ethers.js

Hi @ricmoo, I'm using WebSocketProvider server-side to listen to blockchain events and performing calls to smart contracts. Sometimes the websocket pipe got broken and I need to reconnect it. I use...

github.com

해당 링크에서는 지속적으로 코드가 업데이트 되고 있기 때문에 버전이 바뀜에 따라서 위의 링크를 계속해서 참고해서 만들면 될 것 같다.

const EXPECTED_PONG_BACK = 15000
const KEEP_ALIVE_CHECK_INTERVAL = 7500

export const startConnection = () => {
  provider = new ethers.providers.WebSocketProvider(config.ETH_NODE_WSS)

  let pingTimeout = null
  let keepAliveInterval = null

  provider._websocket.on('open', () => {
    keepAliveInterval = setInterval(() => {
      logger.debug('Checking if the connection is alive, sending a ping')

      provider._websocket.ping()

      // Use `WebSocket#terminate()`, which immediately destroys the connection,
      // instead of `WebSocket#close()`, which waits for the close timer.
      // Delay should be equal to the interval at which your server
      // sends out pings plus a conservative assumption of the latency.
      pingTimeout = setTimeout(() => {
        provider._websocket.terminate()
      }, EXPECTED_PONG_BACK)
    }, KEEP_ALIVE_CHECK_INTERVAL)

    // TODO: handle contract listeners setup + indexing
  })

  provider._websocket.on('close', () => {
    logger.error('The websocket connection was closed')
    clearInterval(keepAliveInterval)
    clearTimeout(pingTimeout)
    startConnection()
  })

  provider._websocket.on('pong', () => {
    logger.debug('Received pong, so connection is alive, clearing the timeout')
    clearInterval(pingTimeout)
  })
}

websocket 웹소켓이 ping을 날리고 응답이 없을 시에는 웹소켓을 죽이고. 웹소켓이 죽을 경우 interval과 timeout을 초기화 한 후에 다시 connection을 만든다.  ping을 날리고 응답이 있다면 pong에 걸리기 때문에 clearInterval을 해준 후에 다음 호출을 또 기다리게 된다. 이런 식으로 지속적으로 ping과 pong을 만들어 주어야 웹소켓을 사용할 수 있다.

해당 정보는

    ethers.providers.WebSocketProvider;

해당 provider로 부터 얻을 수 있다.

 

최신 버전의 경우 다음과 같은 예제가 있었다.

import { Networkish, WebSocketProvider } from "ethers";
import WebSocket from "ws";

const EXPECTED_PONG_BACK = 15000;
const KEEP_ALIVE_CHECK_INTERVAL = 60 * 1000; //7500;

const debug = (message: string) => {
  console.debug(new Date().toISOString(), message);
};

export const ResilientWebsocket = (
  url: string,
  network: Networkish,
  task: (provider: WebSocketProvider) => void
) => {
  let terminate = false;
  let pingTimeout: NodeJS.Timeout | null = null;
  let keepAliveInterval: NodeJS.Timeout | null = null;
  let ws: WebSocket | null;

  const sleep = (ms: number) =>
    new Promise((resolve) => setTimeout(resolve, ms));

  const startConnection = () => {
    ws = new WebSocket(url);
    ws.on("open", async () => {
      keepAliveInterval = setInterval(() => {
        if (!ws) {
          debug("No websocket, exiting keep alive interval");
          return;
        }
        debug("Checking if the connection is alive, sending a ping");

        ws.ping();

        // Use `WebSocket#terminate()`, which immediately destroys the connection,
        // instead of `WebSocket#close()`, which waits for the close timer.
        // Delay should be equal to the interval at which your server
        // sends out pings plus a conservative assumption of the latency.
        pingTimeout = setTimeout(() => {
          if (ws) ws.terminate();
        }, EXPECTED_PONG_BACK);
      }, KEEP_ALIVE_CHECK_INTERVAL);

      const wsp = new WebSocketProvider(() => ws!, network);

      while (ws?.readyState !== WebSocket.OPEN) {
        debug("Waiting for websocket to be open");
        await sleep(1000);
      }

      wsp._start();

      while (!wsp.ready) {
        debug("Waiting for websocket provider to be ready");
        await sleep(1000);
      }

      task(wsp);
    });

    ws.on("close", () => {
      console.error("The websocket connection was closed");
      if (keepAliveInterval) clearInterval(keepAliveInterval);
      if (pingTimeout) clearTimeout(pingTimeout);
      if (!terminate) startConnection();
    });

    ws.on("pong", () => {
      debug("Received pong, so connection is alive, clearing the timeout");
      if (pingTimeout) clearInterval(pingTimeout);
    });

    return ws;
  };

  startConnection();

  return () => {
    terminate = true;
    if (keepAliveInterval) clearInterval(keepAliveInterval);
    if (pingTimeout) clearTimeout(pingTimeout);
    if (ws) {
      ws.removeAllListeners();
      ws.terminate();
    }
  };
};

참고로 아래 예제 또한 매우 잘 되어 있엇다.

https://nownodes.io/blog/how-to-create-javascript-websockets-subscriptions-to-ethereum-and-binance-smart-chain-through-ethers-js/

 

How to Create javascript WebSockets Subscriptions to Ethereum and Binance Smart Chain through ethers.js? | News about Nodes | Th

Create javascript WebSockets Subscriptions through ethers.js. Detailed guide on how to install ethers. BSC WebSocket and Ethereum WebSocket on NOWNodes SaaS

nownodes.io

const { ethers } = require('ethers')
const url = 'wss://bsc.nownodes.io/wss/YOUR_API_KEY'
const EXPECTED_PONG_BACK = 15000
const KEEP_ALIVE_CHECK_INTERVAL = 7500
const startConnection = async () => {
    const provider = new ethers.providers.WebSocketProvider(url, {
        name: 'binance',
        chainId: 56,
    })
    let pingTimeout = null
    let keepAliveInterval = null
    provider._websocket.on('open', () => {
        console.log('Connect')
        keepAliveInterval = setInterval(() => {
            console.log('Checking if the connection is alive, sending a ping')
            provider._websocket.ping()
            // Use WebSocket#terminate(), which immediately destroys the connection,
            // instead of WebSocket#close(), which waits for the close timer.
            // Delay should be equal to the interval at which your server
            // sends out pings plus a conservative assumption of the latency.
            pingTimeout = setTimeout(() => {
                provider._websocket.terminate()
            }, EXPECTED_PONG_BACK)
        }, KEEP_ALIVE_CHECK_INTERVAL)
    })
    provider._websocket.on('close', () => {
        console.error('The websocket connection was closed')
        clearInterval(keepAliveInterval)
        clearTimeout(pingTimeout)
        startConnection()
    })
    provider._websocket.on('pong', () => {
        console.log('Received pong, so connection is alive, clearing the timeout')
        clearInterval(pingTimeout)
    })
    provider.on('block',(block)=>{
        console.log('New block!', block)
    })
}
startConnection()
728x90
반응형
Comments