Websocket Interaction

Last update:2024-09-03 17:00:40

This example demonstrates how to build a complete WebSocket service using a single Edge Cloud Apps Function.

Traditional CDNs offer limited acceleration capabilities for WebSocket traffic. By migrating WebSocket logic to the edge, we can significantly reduce latency, provide a more responsive experience, and alleviate pressure on the origin server.

Example Description

This example shows how a single Edge Cloud Apps Function can create a WebSocket service, including:

  • WebSocket Server: Listens for and handles WebSocket connection requests from clients.
  • HTML Page Generation: Returns an HTML page containing the client-side JavaScript code to establish and interact with the WebSocket connection.

When a user visits the HTML page, the JavaScript code on the page establishes a WebSocket connection with the function. When the user clicks a button on the page, the browser sends a message to the function through the WebSocket. The function processes the message, returns the result, and the browser updates the page with the result in real-time.

The entire interaction takes place on the edge node, eliminating the need for communication with the origin server, leading to lower latency and faster response times.

Code Example

async function handleRequest(request) {
    let { pathname } = new URL(request.url);
    pathname = pathname.toLowerCase();
    if (pathname.endsWith('/')) {
        pathname = pathname.substring(0, pathname.length - 1);
    }
    
    // Choose different processing logic based on the request path
    if (pathname === '/wsclick.html') {
        // Return the HTML page
        return new Response(HOMEPAGE, {headers: {"Content-Type": "text/html; charset=utf-8"}});
    } else if (pathname === '/wsclick') {
        // Handle the WebSocket connection request
        return websocketHandler(request); 
    }
}

// Handle WebSocket connection requests
async function websocketHandler(request) {
    const upgradeHeader = request.headers.get("Upgrade");
    // Check if it's a WebSocket upgrade request
    if (upgradeHeader !== "websocket") {
        return new Response("Expected websocket", { status: 400 });
    }

    // Create a WebSocket connection pair
    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); // Handle the WebSocket session

    // Return the WebSocket response
    return new Response(null, {
        status: 101,
        webSocket: client
    });
}

let count = 0;

// Handle the WebSocket session
async function handleSession(websocket, serverName) {
    websocket.accept(); // Accept the WebSocket connection
    websocket.addEventListener("message", async ({ data }) => {
        console.log('message', data);
        const now = (new Date()).toString().replace(' ()', '');
        if (data === "CLICK") {
            // Handle the "CLICK" message
            count += 1;
            websocket.send(JSON.stringify({ count, tz: now, server: serverName })); // Return the count and server information
        } else {
            // Handle unknown messages
            websocket.send(JSON.stringify({ error: "Unknown message received", tz: now, server: serverName })); 
        }
    });
  
    // Handle connection close event
    websocket.addEventListener("close", async evt => {
        console.log('xxx:close', evt);
    });
}

// HTML page content
const HOMEPAGE = `
<html>
<head>
<title>WebSocket Click Counter Example</title>
<style>
  body {
    margin: 1rem;
    font-family: monospace;
    font-size: 16px;
  }
</style>
</head>
<body>
<p>Click Count: <span id="num"></span></p>
<button id="click">Click to Send Message</button>

<p>You can also send a message that the WebSocket server does not recognize. This will cause the WebSocket server to return an error message to the client.</p>
<button id="unknown">Send Unrecognized Message</button>

<p>Once you are done testing, you can close the connection by clicking the button below. Further clicks will have no effect until you refresh the page.</p>
<button id="close">Close Connection</button>

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

<h4>Received Server WebSocket Messages</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));
});
Is the content of this document helpful to you?
Yes
I have suggestion
Submitted successfully! Thank you very much for your feedback, we will continue to strive to do better!