Overview
Basic Concepts
Quick Start
Manage Functions In Edge Cloud Apps
Function Deployment
Trigger Your Functions
Edge KV
Developer Tools
Examples
Examples Overview
Page Rewriting
Terminal-Based Content Adaptation
Edge Rendering
Edge Website Building
Request Signature and Legitimacy Verification
Multilingual Support with WASM
Site Proxy
Websocket Interaction
Virtual Waiting Room
Runtime APIs
CloudIDE
Limits

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));
});