Websocket Interaction

最終更新日:2024-09-03 17:00:40

この例では、単一のEdge Cloud Apps関数を使用して、完全なWebSocketサービスを構築する方法を示します。

従来のCDNは、WebSocketトラフィックのアクセラレーション機能が制限されています。WebSocketロジックをエッジに移行することにより、レイテンシーを大幅に削減し、より応答性の高いエクスペリエンスを提供し、オリジンサーバーの負荷を軽減できます。

例の説明

この例では、単一のEdge Cloud Apps関数が、以下を含むWebSocketサービスを作成する方法を示します。

  • **WebSocketサーバー:**クライアントからのWebSocket接続リクエストをリッスンして処理します。
  • **HTMLページの生成:**WebSocket接続を確立および対話するためのクライアント側のJavaScriptコードを含むHTMLページを返します。

ユーザーがHTMLページにアクセスすると、ページ上のJavaScriptコードが関数とのWebSocket接続を確立します。ユーザーがページのボタンをクリックすると、ブラウザはWebSocketを介して関数にメッセージを送信します。関数はメッセージを処理し、結果を返し、ブラウザは結果でページをリアルタイムに更新します。

インタラクション全体がエッジノードで行われるため、オリジンサーバーとの通信が不要になり、レイテンシーが短縮され、応答時間が短縮されます。

コード例

async function handleRequest(request) {
    let { pathname } = new URL(request.url);
    pathname = pathname.toLowerCase();
    if (pathname.endsWith('/')) {
        pathname = pathname.substring(0, pathname.length - 1);
    }
    
    // リクエストパスに基づいて、異なる処理ロジックを選択します
    if (pathname === '/wsclick.html') {
        // HTMLページを返します
        return new Response(HOMEPAGE, {headers: {"Content-Type": "text/html; charset=utf-8"}});
    } else if (pathname === '/wsclick') {
        // WebSocket接続リクエストを処理します
        return websocketHandler(request); 
    }
}

// WebSocket接続リクエストを処理します
async function websocketHandler(request) {
    const upgradeHeader = request.headers.get("Upgrade");
    // WebSocketアップグレードリクエストかどうかを確認します
    if (upgradeHeader !== "websocket") {
        return new Response("WebSocketが予期されます", { status: 400 });
    }

    // WebSocket接続ペアを作成します
    const [client, server] = Object.values(new WebSocketPair());
    let serverName = request.headers.get('x-ws-request-id');
    if (serverName) {
      serverName = serverName.match(/_(.*?)_/)[1];
    }
    await handleSession(server, serverName); // WebSocketセッションを処理します

    // WebSocketレスポンスを返します
    return new Response(null, {
        status: 101,
        webSocket: client
    });
}

let count = 0;

// WebSocketセッションを処理します
async function handleSession(websocket, serverName) {
    websocket.accept(); // WebSocket接続を受け入れます
    websocket.addEventListener("message", async ({ data }) => {
        console.log('メッセージ', data);
        const now = (new Date()).toString().replace(' ()', '');
        if (data === "CLICK") {
            // 「CLICK」メッセージを処理します
            count += 1;
            websocket.send(JSON.stringify({ count, tz: now, server: serverName })); // カウントとサーバー情報を返します
        } else {
            // 不明なメッセージを処理します
            websocket.send(JSON.stringify({ error: "不明なメッセージを受信しました", tz: now, server: serverName })); 
        }
    });
  
    // 接続が閉じられたイベントを処理します
    websocket.addEventListener("close", async evt => {
        console.log('xxx:close', evt);
    });
}

// HTMLページのコンテンツ
const HOMEPAGE = `
<html>
<head>
<title>WebSocketクリックカウンターの例</title>
<style>
  body {
    margin: 1rem;
    font-family: monospace;
    font-size: 16px;
  }
</style>
</head>
<body>
<p>クリック回数:<span id="num"></span></p>
<button id="click">クリックしてメッセージを送信</button>

<p>WebSocketサーバーが認識しないメッセージを送信することもできます。これにより、WebSocketサーバーはクライアントにエラーメッセージを返します。</p>
<button id="unknown">認識されないメッセージを送信</button>

<p>テストが完了したら、下のボタンをクリックして接続を閉じることができます。ページを更新するまで、それ以上のクリックは効果がありません。</p>
<button id="close">接続を閉じる</button>

<p id="error" style="color: red;"></p>

<h4>受信したサーバーWebSocketメッセージ</h4>
<ul id="events"></ul>

<script>
  let ws

  async function websocket(url) {
    ws = new WebSocket(url)

    if (!ws) {
      throw new Error("サーバーがwsを受け入れませんでした")
    }

    ws.addEventListener("open", () => {
      console.log('WebSocketを開きました')
      updateCount(0)
    })

    ws.addEventListener("message", ({ data }) => {
      const { count, tz, error } = JSON.parse(data)
      addNewEvent(data)
      if (error) {
        setErrorMessage(error)
      } else {
        setErrorMessage()
        updateCount(count)
      }
    })

    ws.addEventListener("close", () => {
      console.log('WebSocketを閉じました')

      const list = document.querySelector("#events")
      list.innerText = ""
      updateCount(0)
      setErrorMessage()
    })
  }

  const url = new URL(window.location)
  url.protocol = "ws"
  url.pathname = "/wsclick"
  websocket(url)

  document.querySelector("#click").addEventListener("click", () => {
    ws.send("CLICK")
  })

  const updateCount = (count) => {
    document.querySelector("#num").innerText = count
  }

  const addNewEvent = (data) => {
    const list = document.querySelector("#events")
    const item = document.createElement("li")
    item.innerText = data
    list.prepend(item)
  }

  const closeConnection = () => ws.close()

  document.querySelector("#close").addEventListener("click", closeConnection)
  document.querySelector("#unknown").addEventListener("click", () => ws.send("HUH"))

  const setErrorMessage = message => {
    document.querySelector("#error").innerHTML = message ? message : ""
  }
</script>
</body>
</html>
`;
                    
addEventListener("fetch", event => {
    return event.respondWith(handleRequest(event.request));
});