Skip to content

WebSocket proxy in middleware mode #4124

@tomoam

Description

@tomoam

Clear and concise description of the problem

vite's WebSocket proxy doesn't work in middleware mode even if set ws: true like the following.

import vite from 'vite';
import http from 'http';

let handler = (req, res) => {};
const parentServer = http.createServer(handler);

const viteServer = await vite.createServer({
  server: {
    middlewareMode: true,
    proxy: {
      '/ws': {
        target: 'ws://localhost:8080',
        changeOrigin: true,
        ws: true, // even if true, websocket proxy doesn't work.
      }
    }
  }
});

handler = (req, res) => async () => {
  viteServer.middlewares(req, res, async () => {...})
}

parentServer.listen();

Because httpServer is required to set up WebSocket proxy but httpServer becomes null in middleware mode.

const httpServer = middlewareMode
? null
: await resolveHttpServer(serverConfig, middlewares, httpsOptions)

middlewares.use(proxyMiddleware(httpServer, config))

if (httpServer) {
httpServer.on('upgrade', (req, socket, head) => {
const url = req.url!
for (const context in proxies) {
if (url.startsWith(context)) {
const [proxy, opts] = proxies[context]
if (
(opts.ws || opts.target?.toString().startsWith('ws:')) &&
req.headers['sec-websocket-protocol'] !== HMR_HEADER
) {
if (opts.rewrite) {
req.url = opts.rewrite(url)
}
proxy.ws(req, socket, head)

So, we have to add WebSocket proxy code in our code with a way like copy&paste from proxyMiddleware in proxy.ts. This is a little inconvenient.

// add dependency for creating proxyServer
import { httpProxy } from 'http-proxy';

...

parentServer.on('upgrade', () => {
  // 
  // copy & paste from 
  // https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/proxy.ts#L35-L78
  //
}).listen();

Suggested solution

I think it would be very useful to be able to pass the parent server like hmr.

  • vite/src/node/server/index.ts
export interface ServerOptions {
  ...
  
  // current
  // middlewareMode?: boolean | 'html' | 'ssr'

  // this proposal
  middlewareMode?:
    | boolean
    | 'html'
    | 'ssr'
    | { mode: true | 'html' | 'ssr'; parentServer: http.Server }

  ...
}

The following link shows an example implementation.
tomoam@38e54e7

In this case, WebSocket proxy will work in middleware mode, without adding any complicated code.

const parentServer = http.createServer(...);

...

const viteServer = await vite.createServer({
  server: {
    middlewareMode: {
      mode: 'ssr',
      parentServer: parentServer // pass the parent server
    },
    proxy: {
      '/ws': {
        target: 'ws://localhost:8080',
        changeOrigin: true,
        ws: true
      }
    }
  }
});

...

parentServer.listen();

Alternative

export proxyMiddleware.

  • vite/src/node/index.ts
// add export
export { proxyMiddleware } from './server/middlewares/proxy'

The following link shows an example implementation.
tomoam@328610d

In this case, we will need to find and update the configured proxy middleware, but we will not need to copy and paste the code from proxy.ts and import http-proxy, and the impact will be minimal for vite.

...

const parentServer = http.createServer(...);

const viteServer = await vite.createServer({
  server: {
    middlewareMode: true,
    proxy: {
      '/ws': {
        target: 'ws://localhost:8080',
        changeOrigin: true,
        ws: true
      }
    }
  }
});

// find the proxy middleware in ViteDevServer.middlewares 
const index = viteServer.middlewares.stack.findIndex(
  (mw) =>
    mw.route === '' &&
    typeof mw.handle === 'function' &&
    mw.handle.name === 'viteProxyMiddleware'
);

// update the proxy middleware with exported `proxyMiddleware` 
viteServer.middlewares.stack[index].handle = vite.proxyMiddleware(
  parentServer, // pass the parent server
  viteServer.config
);

parentServer.listen();

Additional context

No response

Validations

Metadata

Metadata

Assignees

No one assigned

    Labels

    p3-minor-bugAn edge case that only affects very specific usage (priority)

    Type

    No type

    Projects

    Status

    Discussing

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions