Skip to content

Customize response headers with Express middleware #668

Closed
@thernstig

Description

@thernstig

Is your feature request related to a problem? Please describe.
A common Express middleware needs an interface like (req, res, next). A common example would be helmet() which adds securtiy headers in responses.

But the new headers introduced in https://socket.io/blog/socket-io-4-1-0/#add-a-way-to-customize-the-response-headers does not get the res in the callback:

io.engine.on("initial_headers", (headers, req) => {
  headers["test"] = "123";
  headers["set-cookie"] = "mycookie=456";
});

io.engine.on("headers", (headers, req) => {
  headers["test"] = "789";
});

This means we cannot use common Express middleware to add headers properly.

Describe the solution you'd like
I would have wanted this:

io.engine.on("initial_headers", (headers, req, res) => {
  helmet()(req, res);
});

io.engine.on("headers", (headers, req) => {
  helmet()(req, res);
});

Describe alternatives you've considered
Our current solution is this which is a hack we want to replace with the new solution of io.engine.on("initial_headers" and io.engine.on("headers"

/**
 * Attach the socket.io server to an HTTP(S) server.
 *
 * @param {http.Server|https.Server} server - Server to attach to
 */
function attachSocketIoToServer(server) {
  const socketIoPath = `socket.io`;
  io.attach(server, {
    serveClient: false,
    path: socketIoPath,
  });

  // Workarounds to add secure headers to socket.io responses. Different
  // workarounds are needed for polling and websocket transports.
  //
  // 1. Polling transport: When attaching to a server, socket.io removes all
  //    other listeners of the 'request' event and adds itself. If the path
  //    matches the socket.io path it handles the request, otherwise it calls
  //    the other listeners itself.
  //
  //    By doing the same thing, we can use Helmet to add the headers to the
  //    response before socket.io gets involved.
  if (server.listeners('request').length !== 1) {
    throw new Error('Unexpected number of "request" listeners on server');
  }
  const socketIoListener = server.listeners('request')[0];
  server.removeAllListeners('request');
  server.on('request', (req, res) => {
    // @ts-ignore -- Fault in Node.js types, see https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/54920
    if (req.url.startsWith(socketIoPath)) {
      helmet(getHelmetOptions())(req, res, () => {
        socketIoListener(req, res);
      });
    } else {
      socketIoListener(req, res);
    }
  });
  // 2. Websocket transport: The underlying 'ws' package emits a 'headers' event
  //    when the headers are ready, which is meant to be used for inspecting or
  //    modifying the headers before they are sent.
  //
  //    We hook into this after the server has begun listening, since engine.io
  //    recreates the ws object at that point.
  server.on('listening', () => {
    io.eio.ws.on('headers', (headers) => {
      headers.push(
        'something something'
      );
    });
  });
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions