Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebSocket proxy in middleware mode #4124

Closed
4 tasks done
tomoam opened this issue Jul 5, 2021 · 6 comments · Fixed by #14632
Closed
4 tasks done

WebSocket proxy in middleware mode #4124

tomoam opened this issue Jul 5, 2021 · 6 comments · Fixed by #14632
Labels
p3-minor-bug An edge case that only affects very specific usage (priority)

Comments

@tomoam
Copy link

tomoam commented Jul 5, 2021

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:/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

@tomoam
Copy link
Author

tomoam commented Jul 9, 2021

If you approve this proposal, I would like to work on this and create a PR.

@MatthiasGrandl
Copy link

I just stumbled across this, while writing a vite plugin. httpServer is not exposed so there is no way to listen for upgrade. Not sure how to circumvent this issue right now, without resorting to ugly workarounds.

@patak-dev
Copy link
Member

I think we could add a server.proxy.server option here following

. I dont know if the hmr and proxy servers ever need to be different. A PR with a proposal and detailing use cases would be good so we can discuss it with other maintainers.

@FossPrime
Copy link

FossPrime commented Sep 2, 2022

I just stumbled across this, while writing a vite plugin. httpServer is not exposed so there is no way to listen for upgrade. Not sure how to circumvent this issue right now, without resorting to ugly workarounds.

Is this the only hinderance? Socket.io supports setting upgrade false and transport to WebSockets only. if the WebSockets proxy is doing it's job in vite, our WS server should respond throught the proxy, even if our WS server is only listening for vite's proxy server traffic.

I'm going to try to make a sandbox with a websockets proxy setup today. I'm looking to try vite-node if I find vite disagreeable.

@robertsLando
Copy link

News on this? I have to proxy websockets and I cannot make them work with vite anyway

@patrickelectric
Copy link

Two years and no fix on that ? This is a critical feature.

@antfu antfu added p3-minor-bug An edge case that only affects very specific usage (priority) and removed enhancement: pending triage labels Oct 15, 2023
@github-actions github-actions bot locked and limited conversation to collaborators Nov 30, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
p3-minor-bug An edge case that only affects very specific usage (priority)
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

7 participants