start-companion-with-load-balancer.mjs 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. #!/usr/bin/env node
  2. import http from 'node:http'
  3. import httpProxy from 'http-proxy'
  4. import process from 'node:process'
  5. import { execaNode } from 'execa';
  6. const numInstances = 3
  7. const lbPort = 3020
  8. const companionStartPort = 3021
  9. // simple load balancer that will direct requests round robin between companion instances
  10. function createLoadBalancer (baseUrls) {
  11. const proxy = httpProxy.createProxyServer({ ws: true })
  12. let i = 0
  13. function getTarget () {
  14. return baseUrls[i % baseUrls.length]
  15. }
  16. const server = http.createServer((req, res) => {
  17. const target = getTarget()
  18. // console.log('req', req.method, target, req.url)
  19. proxy.web(req, res, { target }, (err) => {
  20. console.error('Load balancer failed to proxy request', err.message)
  21. res.statusCode = 500
  22. res.end()
  23. })
  24. i++
  25. })
  26. server.on('upgrade', (req, socket, head) => {
  27. const target = getTarget()
  28. // console.log('upgrade', target, req.url)
  29. proxy.ws(req, socket, head, { target }, (err) => {
  30. console.error('Load balancer failed to proxy websocket', err.message)
  31. console.error(err)
  32. socket.destroy()
  33. })
  34. i++
  35. })
  36. server.listen(lbPort)
  37. console.log('Load balancer listening', lbPort)
  38. return server
  39. }
  40. const isWindows = process.platform === 'win32'
  41. const isOSX = process.platform === 'darwin'
  42. const startCompanion = ({ name, port }) => execaNode('packages/@uppy/companion/src/standalone/start-server.js', {
  43. nodeOptions: [
  44. '-r', 'dotenv/config',
  45. // Watch mode support is limited to Windows and macOS at the time of writing.
  46. ...(isWindows || isOSX ? ['--watch-path', 'packages/@uppy/companion/src', '--watch'] : []),
  47. ],
  48. cwd: new URL('../', import.meta.url),
  49. stdio: 'inherit',
  50. env: {
  51. // Note: these env variables will override anything set in .env
  52. ...process.env,
  53. COMPANION_PORT: port,
  54. COMPANION_SECRET: 'development', // multi instance will not work without secret set
  55. COMPANION_PREAUTH_SECRET: 'development', // multi instance will not work without secret set
  56. COMPANION_ALLOW_LOCAL_URLS: 'true',
  57. COMPANION_ENABLE_URL_ENDPOINT: 'true',
  58. COMPANION_LOGGER_PROCESS_NAME: name,
  59. COMPANION_CLIENT_ORIGINS: 'true',
  60. },
  61. })
  62. const hosts = Array.from({ length: numInstances }, (_, index) => {
  63. const port = companionStartPort + index
  64. return { index, port }
  65. })
  66. console.log('Starting companion instances on ports', hosts.map(({ port }) => port))
  67. const companions = hosts.map(({ index, port }) => startCompanion({ name: `companion${index}`, port }))
  68. let loadBalancer
  69. try {
  70. loadBalancer = createLoadBalancer(hosts.map(({ port }) => `http://localhost:${port}`))
  71. await Promise.all(companions)
  72. } finally {
  73. loadBalancer?.close()
  74. companions.forEach((companion) => companion.kill())
  75. }