Websocket交互

更新时间:2024-09-03 17:00:40

本示例展示如何利用 Edge Cloud Apps 函数在边缘节点上构建WebSocket服务,并通过一个简单的网页演示WebSocket的实时交互功能。
传统CDN对WebSocket流量的加速能力有限,将WebSocket逻辑迁移至边缘,可以显著降低延迟,提供更灵敏的响应体验,并减轻源站服务器的压力。

示例演示了如何使用单个Edge Cloud Apps函数,构建一个完整的WebSocket服务,包含以下功能:

  • WebSocket服务器: 监听WebSocket连接请求,并处理客户端发送的消息。
  • HTML页面生成: 返回一个包含WebSocket客户端代码的 TML页面,该代码与WebSocket服务器进行交互。

用户访问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("Expected 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('message', 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: "Unknown message received", 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服务器向客户端返回一个error消息。</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("server didn't accept ws")
    }

    ws.addEventListener("open", () => {
      console.log('Opened 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('Closed 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));
});
本篇文档内容对您是否有帮助?
有帮助
我要反馈
提交成功!非常感谢您的反馈,我们会继续努力做到更好!