Support of cloudflare workers with websockets

Hi, Cloudflare Community! We want to implement security headers through the cloudflare workers. I have written workers script for that working perfectly, but some of our subdomains like real-time chat system uses websockets to communicate between client’s browser and servers.

Now the problem here is that those websocket connections get failed and when I start Log Stream to check the logs of workers then following logs are shown

Exception
GET
https:/mydomain.com/socket.io/?EIO=4&transport=websocket
{
  "outcome": "exception",
  "scriptName": null,
  "exceptions": [
    {
      "name": "RangeError",
      "message": "Responses may only be constructed with status codes in the range 200 to 599, inclusive.",
      "timestamp": 
    }
  ],
  "logs": [],
  "eventTimestamp": ,
  "event": {
    "request": {
      "url": "https://mydomain.com/socket.io/?EIO=4&transport=websocket",
      "method": "GET",
      "headers": {
        "accept-encoding": "gzip",
        "accept-language": "en-US,en;q=0.9",
        "cache-control": "no-cache",
        "cf-connecting-ip": "",
        "cf-ipcountry": "",
        "cf-ray": "",
        "cf-visitor": "{\"scheme\":\"https\"}",
        "connection": "Upgrade",
        "host": "",
        "origin": "",
        "pragma": "no-cache",
        "sec-websocket-extensions": "permessage-deflate; client_max_window_bits",
        "sec-websocket-key": "REDACTED",
        "sec-websocket-version": "13",
        "upgrade": "websocket",
        "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36",
        "x-forwarded-proto": "https",
        "x-real-ip": ""
      },
      "cf": {
        "clientTcpRtt": 644,
        "longitude": "",
        "latitude": "",
        "tlsCipher": "AEAD-AES128-GCM-SHA256",
        "continent": "AS",
        "asn": 9541,
        "clientAcceptEncoding": "gzip, deflate, br",
        "country": "",
        "tlsClientAuth": {
          "certIssuerDNLegacy": "",
          "certIssuerSKI": "",
          "certSubjectDNRFC2253": "",
          "certSubjectDNLegacy": "",
          "certFingerprintSHA256": "",
          "certNotBefore": "",
          "certSKI": "",
          "certSerial": "",
          "certIssuerDN": "",
          "certVerified": "NONE",
          "certNotAfter": "",
          "certSubjectDN": "",
          "certPresented": "0",
          "certRevoked": "0",
          "certIssuerSerial": "",
          "certIssuerDNRFC2253": "",
          "certFingerprintSHA1": ""
        },
        "tlsExportedAuthenticator": {
          "clientFinished": "f525ec3a95466687d87b5464471209e3def152eabbb538d66e3d4e7fcd369c9c",
          "clientHandshake": "02347b4197cbf65a81e61c9e31ce65f26a23e4474826e2fdfcc603f6b3bedf69",
          "serverHandshake": "0952aa3b1a12269ad55e48491fdcebbcd9",
          "serverFinished": "8aa2696ba4b723019db592335717117df3c"
        },
        "tlsVersion": "TLSv1.3",
        "colo": "MRS",
        "timezone": "",
        "city": "",
        "edgeRequestKeepAliveStatus": 1,
        "requestPriority": "",
        "httpProtocol": "HTTP/1.1",
        "region": "",
        "regionCode": "",
        "asOrganization": "",
        "postalCode": ""
      }
    }
  },
  "id": 79

As you know that websockets works using 101 status code. Then I read about websockets supports through the CF-Workers. I used the following JS code to test the websocket’s communication

async function handleRequest(request) {
  const upgradeHeader = request.headers.get("Upgrade")
  if (!upgradeHeader || upgradeHeader !== "websocket") {
    return new Response("Expected Upgrade: websocket", { status: 426 })
  }


  const webSocketPair = new WebSocketPair()
  const [client, server] = Object.values(webSocketPair)

  server.accept()

  server.addEventListener("message", event => {
    console.log(event.data)
  })

  return new Response(null, {
    status: 101,
    webSocket: client
  })
}

addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request));
});

and mentioned the following in “Triggers”

mydomain.com/socket.io/*

And started Monitoring Logs but I am unable to see any logs or hit. Actually, We want to implement security headers through cloudflare workers and it should not affect any websocket communication being used in different subdomains.
Please guide how it is possible through Cloudflare Workers.

This article might help, also keep in mind that you won’t see any console logs until there’s a “message” event happening in the example you posted.

Also, how many connections are you going to keep opened? Because CF have “hidden” limits on that and if you reach them (I’d assume +10K connections open) then they’ll require an enterprise account.

Thanks for your response. I’ve tried it. But have you really got my problem? I think your provided solution will work in case if we want to setup websocket server on Cloudflare level but in my case I have to make sure establishment of connections successfully between my domain server and client through websockets. is it possible to setup websockets in such a way that it works as proxy between websocket server and websocket client so that it can redirect all communications between websocket client and websocket server?

If you mean Browser to your Origin, no, CF workers cannot function as a connection router/relay. If that is what you need, it’s better that you look at a WebRTC Relay server and use WebRTC for the connections. (Which is a protocol CF network doesn’t support yet).

If you mean a websocket passthrough Client → Cloudflare → Origin, then I don’t recommend it because of the 50 sub-request limit. It is likely that connections will be broken and trying to reconnect over and over.

I appreciate your attention on it. Kindly have a look at the attached image.

The node server cannot/should not connect to the Cloudflare worker, you can only use the worker as a proxy not an intermediary for both sides. Which means that there’s no advantage to using CF workers, your client’s can just connect directly to your node servers websocket.

See this if you still want to use CF as a proxy towards node.

You are right. Client’s can directly connect to Node Server. This is what happening currently. But let’s say my domain node.mydomain.com is running websocket server and its traffic is being proxied by Cloudflare Proxy (orange cloud) and we have to implement security headers on it through Cloudflare workers. When we apply CF worker on node.mydomain.com then it starts throwing exception that it can only handle responses between 200-599. But do you think that by using CF worker as a proxy will be a solution for this problem? If yes, then how? What will be the code (sample) of CF worker which will be acting as proxy? I have seen your link, this is what if Cloudflare worker will be working as Client and it will connect to my node server (websocket server) then how it will make sure the establishment of client’s (end user) connection with node server?

Websockets is only 1 connection, if the client request a chat he makes a connection and that connection is a tunnel and it’s always open, when that happens, node can send messages to the client and the client can send messages to node. Node shouldn’t connect to anything, it only accepts the connection from CF network (Initiated by the client).