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

proxy.bypass should support async functions #18920

Open
7 tasks done
johncrim opened this issue Dec 9, 2024 · 1 comment · May be fixed by #18940
Open
7 tasks done

proxy.bypass should support async functions #18920

johncrim opened this issue Dec 9, 2024 · 1 comment · May be fixed by #18940
Labels
contribution welcome enhancement New feature or request p2-nice-to-have Not breaking anything but nice to have (priority)

Comments

@johncrim
Copy link

johncrim commented Dec 9, 2024

Describe the bug

I'm migrating our angular projects from webpack to esbuild/vite, and ran into a problem with the vite proxy: Any non-synchronous calls in proxy.bypass result in errors, though they kinda work:

  1. If I return a Promise from the bypass function, I get a compile or typing error:
error TS2322: Type '(req: IncomingMessage, res: ServerResponse<IncomingMessage>, proxyOptions: ProxyOptions) => false | Promise<...> | undefined' is not assignable to type '(req: IncomingMessage, res: ServerResponse<IncomingMessage>, options: ProxyOptions) => string | false | void | null | undefined'.
  Type 'false | Promise<boolean> | undefined' is not assignable to type 'string | false | void | null | undefined'.
    Type 'Promise<boolean>' is not assignable to type 'string | false | void | null | undefined'.

I can tell from the code that there's no explicit support for Promises in the bypass implementation, and there are no tests, but interestingly it kinda works...

  1. If I ignore / work around the type error, I get this error in the console for each proxied request:
8:14:57 PM [vite] http proxy error: Must provide a proper URL as target
Error: Must provide a proper URL as target
    at ProxyServer.<anonymous> (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:39678:36)
    at viteProxyMiddleware (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:40021:15)
    at call (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22418:7)
    at next (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22362:5)
    at viteCachedTransformMiddleware (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:42209:5)
    at call (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22418:7)
    at next (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22362:5)
    at cors (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22890:7)
    at file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22926:17
    at originCallback (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22916:15)

BUT the async call does complete successfully, and the browser receives the correct async proxied result.

  1. If I add a placeholder / fake proxy.target value (as the error message suggests), it is used instead and resolves, and when my async bypass function completes it fails because the response has already been written. So I get 2 errors, eg:
8:18:45 PM [vite] http proxy error: /foo
Error: getaddrinfo ENOTFOUND not.localhost
    at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:120:26)
node:_http_server:342
    throw new ERR_HTTP_HEADERS_SENT('write');
    ^

Error [ERR_HTTP_HEADERS_SENT]: Cannot write headers after they are sent to the client
    at ServerResponse.writeHead (node:_http_server:342:11)
    at Timeout._onTimeout (file:///C:/src/github/johncrim/vite-bugs/node_modules/.vite-temp/vite.config.ts.timestamp-1733717919201-ba46a9b806b2d.mjs:9:17)
    at listOnTimeout (node:internal/timers:581:17)
    at process.processTimers (node:internal/timers:519:7) { 

Note that webpack's proxy bypass does support Promises.

Reproduction

https://github.com/johncrim/vite-bugs

Steps to reproduce

git checkout https://github.com/johncrim/vite-bugs
yarn
yarn dev --config vite.config.ts

Open the browser, then visit the /foo URL. The page displays after 3 seconds, but the error listed above is shown in the terminal.

I provided 2 forms of non-sychronous bypass functions, with and without Promises:

without Promises:

// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    proxy: {
      '/foo': {
        bypass: (req, res, options) => {
          setTimeout(() => {
            res.writeHead(200, {
              'Content-Type': 'text/plain'
            });
            res.end('Hello after 3 seconds (setTimeout)')
          }, 3000);
        },
        // The error message says a target is required, but
        // uncommenting this completely breaks the proxy.
        //target: 'http://not.localhost'
      }
    }
  }
})

with Promises:

// vite.config2.ts
import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    proxy: {
      '/foo': {
        bypass: async (req, res, options) => {
          await timeout(3000);
          res.writeHead(200, {
            'Content-Type': 'text/plain'
          });
          res.end('Hello after 3 seconds (async timeout)')
        }
      }
    }
  }
})

function timeout(ms: number) {
  return new Promise<void>((resolve, reject) => {
    setTimeout(() => resolve(), ms);
  })
}

Both have the same problem. I think supporting false | string | void | Promise<false | string | void> for the bypass return type would be ideal, and at a minimum the viteProxyMiddleware fn should exit after the bypass function is called if it doesn't return a string or false. Ideally it would wait for the Promise to resolve, handle string and false the same way they're currently handled, and exit for any other Promise result.

IMO bypass should actually bypass the proxy pipeline, except in the case of 404 or "new target URL returned".

System Info

System:
    OS: Windows 11 10.0.22631
    CPU: (20) x64 12th Gen Intel(R) Core(TM) i9-12900H
    Memory: 6.24 GB / 31.68 GB
  Binaries:
    Node: 22.6.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.19 - C:\Program Files\nodejs\yarn.CMD
    npm: 10.8.2 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Chromium (128.0.2739.67)
    Internet Explorer: 11.0.22621.3527
  npmPackages:
    vite: ^6.0.1 => 6.0.3

Used Package Manager

yarn

Logs

I don't believe the startup info is useful - this is a vanilla starter project with 2 custom config files. The error log is:

Error info inside
  vite:proxy /foo -> undefined +0ms
8:34:18 PM [vite] http proxy error: Must provide a proper URL as target
Error: Must provide a proper URL as target
    at ProxyServer.<anonymous> (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:39678:36)
    at viteProxyMiddleware (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:40021:15)
    at call (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22418:7)
    at next (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22362:5)
    at viteCachedTransformMiddleware (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:42209:5)
    at call (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22418:7)
    at next (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22362:5)
    at cors (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22890:7)
    at file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22926:17
    at originCallback (file:///C:/src/github/johncrim/vite-bugs/node_modules/vite/dist/node/chunks/dep-yUJfKD1i.js:22916:15)
  vite:time 3003.82ms /foo +0ms

Validations

@johncrim
Copy link
Author

johncrim commented Dec 9, 2024

Here's another issue from 2.5 years ago asking for async proxy rewriting - the workaround the issue author came up with isn't an option for us b/c Angular doesn't support custom vite plugins.

If async bypass were supported, I think it makes sense to add async proxy rewriting at the same time. Async bypass could be used to implement async proxy rewriting from a user standpoint, but it seems cleaner to support both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution welcome enhancement New feature or request p2-nice-to-have Not breaking anything but nice to have (priority)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants