Skip to content

Service async function on onProxyReq  #318

Open

Description

@pawelszczerbicki

Having proxy configured with onProxyReq like:

proxy(gatewayUrl, {
  target: destinationUrl,
  pathRewrite: { [`^${gatewayUrl}`]: '' },
  changeOrigin: true,
  logProvider: () => logger,
  ...options,
  onProxyReq: async (proxyReq, req) => await addAuthHeader(proxyReq, req, redis),
});

Middleware is finishing request before promise is resolved. It is possible to service promise in proxy functions ?

Activity

  1. julkue commented on Jan 24, 2019

    @julkue

    @pawelszczerbicki Having the same question. I will probably use node-http-proxy instead, as there's a documented example for this case.

  2. magnusp commented on Feb 18, 2019

    @magnusp

    I was also looking at making external calls to add/verify authorization headers but came to the same conclusion. I'm guessing that a viable option is to have another middleware do the external call and have it mutate incoming request headers (would transfer over to onProxyReq). But it would be really nice to be able to do this within http-proxy-middleware having matched the route/pattern of the incoming request.

  3. chimurai commented on Feb 19, 2019

    @chimurai
    Owner

    Looks like there is some demand for async listeners.
    I'll consider implementation after some investigation.

    @julmot Can you point to the documented node-http-proxy example?

    @magnusp Think you can to achieve the same result by chaining middlewares.
    Assuming you use express you can write a authValidator and mount it in express like:
    app.use('/some/endpoint', authValidator, proxy)

  4. magnusp commented on Feb 19, 2019

    @magnusp

    Im using it via webpack-dev-server which has a configuration property for http-proxy-middleware. The devserver also has a way to setup additional middleware via the "before" and "after" configuration properties. This leaves me having a choice of using my middleware+http-proxy-middleware in a "before" or only setting up my middleware in "before" and using the dedicated "proxy" configuration property. Im mostly mentioning this here for other people stumbling upon the issue and letting them see the options available. But async listeners is an awesome feature. I remember wanting it when implementing a chaos monkey where delays on the proxy were introduced randomly and configurably, at runtime.

  5. SreeBS commented on Feb 20, 2019

    @SreeBS

    @chimurai I have to deal with promises in both proxyReq and proxyRes functions. Would be very helpful to have to have these functions support promises.

  6. montera82 commented on Feb 28, 2019

    @montera82

    +1 one for async

    how can I achieve using the result of an async call there?

    i am doing something like this

    onst buildingConfProxyOptions = {
      target: TARGET_BASE_URL,
      changeOrigin: true,
      pathRewrite: {
        '^/building-conf': ''
      },
      async onProxyReq(proxyReq: any, req: any, res: any) {
        const token = req.headers['authorization'];
        const userSvc: UserService = Container.get(UserService);
        const result = await userSvc.verify(token); // Its ending here
    // BUT I NEED TO USE RESULT HERE!
    
      },
      loglevel: 'debug'
    };
    
  7. shian15810 commented on Mar 28, 2019

    @shian15810

    For temporary workaround,
    I'm doing it like this with promise:

    onProxyReq: (proxyReq, req, res) => {
      proxyReq.socket.pause();
      aPromise(a, b).then((c) => {
        proxyReq.setHeader('x-added', c);
        proxyReq.socket.resume();
      }).catch((err) => {
        console.error(err);
        res.sendStatus(500);
      });
    },

    Or with callback function:

    onProxyReq: (proxyReq, req, res) => {
      proxyReq.socket.pause();
      aFunctionWithCallback(a, b, (err, c) => {
        if (err) {
          console.error(err);
          res.sendStatus(500);
          return;
        }
        proxyReq.setHeader('x-added', c);
        proxyReq.socket.resume();
      });
    },

    Is this plausible at all?

  8. boxcee commented on Mar 28, 2019

    @boxcee

    @shian15810 Did you try? Does it work?

  9. shian15810 commented on Mar 28, 2019

    @shian15810

    Yeah, it works!
    By plausible I mean does this really make sense?

  10. magnusp commented on Mar 28, 2019

    @magnusp

    Seems reasonable. I'm guessing that the proxyReq.socket should probably be aborted when an error occurs otherwise it could be left hanging until aborted by an timeout in either end.

  11. shian15810 commented on Mar 29, 2019

    @shian15810

    @magnusp Do you mean something like proxyReq.socket.end() after res.sendStatus(500)?

    Or should it be better off if I get rid of res.sendStatus(500) and use proxyReq.socket.end('HTTP/1.1 500 Internal Server Error\r\n\r\n') instead?

    Edited 1:

    Just check the source code in express, express's res.sendStatus will trigger res.send within itself and in the end trigger node's http res.end.

    Edited 2:

    Just tested, socket.resume is not necessary when using res.sendStatus.

  12. darewreck54 commented on May 1, 2019

    @darewreck54

    @shian15810 How did you set the header. When i try to set the header via proxyReq.setHeader('a','g'), i get an exception throwing

    Cannot set header after they are sent to the client.

    how did you get around that part? I followed the first promise example

    Also not sure what the deal is but the res object has not sendStatus() method.....

    Thanks!

  13. shian15810 commented on May 2, 2019

    @shian15810

    @darewreck54 I was having this issue too, and after looking into the source code, at least for me, I couldn't find any way to circumvent this situation, even when using req.headers[customHeader].

    But having custom header is essential to the functioning of my application. So finally I decided to change the whole architecture to microservice-based. Sorry, couldn't be much of your help.

    If you don't need custom header for your application to be functional, the hack I proposed earlier is still a feasible way to get around the original issue.

    Using req.headers[customHeader] won't give you that annoying error, but the header won't reach the upstream server anyway.

  14. darewreck54 commented on May 2, 2019

    @darewreck54

    I actually end up creating my own express middleware that execute the promise and when its successful it will trigger next(). I'll store the updated header in the request and send it to the proxy middleware

  15. kavitalikesfajitas commented on Jul 24, 2019

    @kavitalikesfajitas

    @chimurai did you create a solution to this? Also if not can you provide an example of the authValidator you mentioned?

  16. 1 remaining item

  17. dpetrov commented on Feb 4, 2020

    @dpetrov

    @shian15810 I somehow can't get it quite to work:

    function onProxyReq(proxyReq, req, res) {
            proxyReq.socket.pause();
            setTimeout(function() {
                    proxyReq.setHeader('X-AUTH-USERNAME', 'admin');
                    proxyReq.socket.resume();
            }, 1000);
    }
    

    which results to:

    throw new ERR_HTTP_HEADERS_SENT('set');
    ^
    
    Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
        at ClientRequest.setHeader (_http_outgoing.js:470:11)
    

    Is anything that I might be missing?

  18. dpetrov commented on Feb 4, 2020

    @dpetrov

    So for the others that might be looking, I've ended up as well writing a middleware that stores the value into the req. For example:

    app.use(function (req, res, next) {
      aFunctionWithCallback((err, res) => {
        req._username = res.username;
        next();
      });
    });
    

    and later on:

      "onProxyReq": function(proxyReq, req) {
        proxyReq.setHeader('X-AUTH-USERNAME', req._username);
      },
    

    Probably not plausible at all, but it does work.

  19. revolunet commented on Apr 17, 2020

    @revolunet

    Thanks @dpetrov this works here too. you dont even have to use onProxyReq, headers will get forwarded by the proxy later on

    server.use((req, res, next) => {
      if (req.path === "/graphql" && req.method === "POST") {
        getSomethingAsync(req.session.id, (err, data) => {
          if (data) {
            req.headers["Authorization"] = `Bearer ${data.user.token}`
          }
          next()
        })
      } else {
        next()
      }
    })
  20. kwiss commented on May 1, 2020

    @kwiss

    no way @revolunet you are here too

  21. Austio commented on May 29, 2020

    @Austio

    Are there any constraints in the library that would block a community PR for this @chimurai?

    Was able to work around with a middleware that fires before my proxy middleware

  22. smileatom commented on Oct 2, 2020

    @smileatom

    There was a PR last year on http-proxy for this. And without async support in handlers even though it might be somewhat trivial to implement, I didnt really get a warm fuzzy when I looked at the internals.

    Ill just use a library with keep alive and call it good.

  23. maapteh commented on Nov 11, 2020

    @maapteh
    Contributor

    @chimurai Steven bouw deze feature even erin man :)

    we also want the async option, just before we stream back the results we want to alter header with async data.

    i will checkout the mentioned node-http-proxy because it has selfHandleResponse option

  24. shihshen commented on Nov 19, 2020

    @shihshen

    I tried with selfHandleResponse option, and it seems working in this middleware as well.

  25. maapteh commented on Nov 19, 2020

    @maapteh
    Contributor

    @chimurai Steven, ik heb hem werkend :)

    So i have this setup where i can still alter the response header AFTER i get the response from the proxied endpoint and do async stuff. In this sample i also included the fake server im mocking to (3001). Maybe nice for your recipe? Its setting response header on the response AFTER getting the response and do async stuff.

    https://codesandbox.io/s/holy-resonance-yz552?file=/src/index.js

    curl -I http://localhost:3000 gives now:

    HTTP/1.1 200 OK
    X-Powered-By: Express
    mpth-2: da
    Date: Thu, 19 Nov 2020 21:53:43 GMT
    Connection: keep-alive
    

    where mpth-2 is set async.

    all i had to do was use
    selfHandleResponse: true, and
    proxyRes.pipe(res); just by digging in the code :(

    thanks @powpowshen for telling it is possible, it made me look again :)

  26. zhuibo66 commented on Aug 14, 2021

    @zhuibo66

    感谢@dpetrov这里也行得通。你甚至不必使用, 头稍后会由代理转发onProxyReq

    server.use((req, res, next) => {
      if (req.path === "/graphql" && req.method === "POST") {
        getSomethingAsync(req.session.id, (err, data) => {
          if (data) {
            req.headers["Authorization"] = `Bearer ${data.user.token}`
          }
          next()
        })
      } else {
        next()
      }
    })

    your answer is good

  27. sarumont commented on Sep 2, 2021

    @sarumont

    Just a note to anyone else looking - the posted workaround which pauses the socket seems to not work with a Websocket upgrade.

    Edit:

    I did find a workaround here - handle the server's upgrade and call proxy.upgrade from your async handler:

    const server = expressApp.listen(8080);
    server.on('upgrade', async (req: Request, socket: Socket, head: Buffer) => {
      someAsyncFunction()
        .then(() => {
          // do what you need with the results of the async function here
          if (upstreamProxy.upgrade) {
            upstreamProxy.upgrade(req, socket, head);
          }
        });
    });
  28. blastshielddown commented on Nov 3, 2021

    @blastshielddown

    @dpetrov and @revolunet you are lifesavers. This worked for me 👍 Thank you

    server.use((req, res, next) => {
      if (req.path === "/graphql" && req.method === "POST") {
        getSomethingAsync(req.session.id, (err, data) => {
          if (data) {
            req.headers["Authorization"] = `Bearer ${data.user.token}`
          }
          next()
        })
      } else {
        next()
      }
    })
  29. added a commit that references this issue on Oct 17, 2022
  30. Hosstell commented on Jun 19, 2024

    @Hosstell

    @pawelszczerbicki
    delete http-proxy-middleware and use just http-proxy

    my example:

    import http from 'http';
    import httpProxy from 'http-proxy';
    
    var proxy = httpProxy.createProxyServer({});
    
    const handler = async (req: any, res: any) => {
        
        const status = await checker(req)
        if (!status) {
           
           res.statusCode = 403
           res.write("Forbidden")
           res.end()
        }
        
        proxy.web(req, res, {
            target: target_host
        });
    }
    
    var server = http.createServer(handler);
    
    server.listen(3000);
  31. dschweinbenz commented on Aug 14, 2024

    @dschweinbenz

    2024: Still no async/await support :/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions