socketio / socket.io

Realtime application framework (Node.JS server)
https://socket.io
MIT License
60.66k stars 10.09k forks source link

HTTP/2 Web Socket in Chrome not working #5067

Open ackava opened 5 months ago

ackava commented 5 months ago

Describe the bug Creating HTTP/2 server mentioned in the example works well with Firefox because Firefox still uses HTTP/1.1 to connect to web server. However, chrome has implemented CONNECT method of HTTP/2, and thus socket.io no longer connects with HTTP/2 server.

Chrome 120 still connects Web Socket over HTTP 1.1, but this stops working from 121 onwards.

To Reproduce

  1. Juse use simple socket.io sample to create HTTP/2 server with SSL certificate.
  2. Try to connect to socket.io server from Google Chorme.

Expected behavior It should connect to socket.io server, but it does not.

Platform:

Additional context For HTTP/1.1 , http2Server emits upgrade event, which engine.io handles correctly. For HTTP/2, http2Server does not emit upgrade event if web socket client sends HTTP-METHOD='CONNECT'.

Work Around/Recommendation

httpServer.prependListener("stream", (stream, headers) => {

    // we need to look for CONNECT
    if(headers[":method"] !== "CONNECT") {
        return;
    }

    try {

      // this keeps socket alive...
      stream.setTimeout(0);
      (stream as any).setKeepAlive?.(true, 0);
      (stream as any).setNoDelay = function() {
          // this will keep the stream open
      };

      const websocket = new WebSocket(null, void 0, {
          headers
      });
      websocket.setSocket(stream, Buffer.alloc(0), {
          maxPayload: 104857600,
          skipUTF8Validation: false,
      });
      const path = headers[":path"];
      const url = new URL(path, "http://a");
      const _query = {};
      for (const [key, value] of url.searchParams.entries()) {
          _query[key] = value;
      }
      // fake build request
      const req = {
          url: path,
          headers,
          websocket,
          _query
      };
      stream.respond({
          ":status": 200
      });
     // for some reason this is not working !!
      this.handshake("websocket",req,() => { try { stream.end(); } catch {} }).catch(console.error);
  } catch (error) {
      console.error(error);
  }
});
darrachequesne commented 5 months ago

I could indeed reproduce the issue, thanks for raising this.

Another possible workaround is to start the HTTP/2 server with allowHTTP1: true:

const server = createSecureServer(
  {
    key: readFileSync("./key.pem"),
    cert: readFileSync("./cert.pem"),
    allowHTTP1: true,
  }
);
const io = new Server(server);

Related: https://github.com/websockets/ws/issues/1458

ackava commented 5 months ago

@darrachequesne allowHTTP1 no longer works with chrome version 121 onwards, it used to work till 120.