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

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