Repository: chimurai/http-proxy-middleware
Pull request #379 (2019-12-12 11:31:56)

feat: async router
hello,
this PR adds support for **options.router** for async handlers.

It came up in this issue: https://github.com/chimurai/http-proxy-middleware/issues/153

I would be glad to also add tests, but it seems like instructions are missing since it expects a running server on port 8000/3000.. please provide and i'll complete the tests

deepcode-ci-bot[bot]: [**DeepCode Report (#c6b8f6)**](https://www.deepcode.ai/app/gh/chimurai/http-proxy-middleware/3b9730826187c708fbd16cb0baa588f6aad73a00/chimurai/http-proxy-middleware/c6b8f6b13e71c656ad45a03895eb8dff46bbd6af/_/%2F/code/)
---
DeepCode analyzed this pull request.
There are no new issues. 
chimurai: Thanks for the PR.

You can run the tests with `yarn test`

The port issue your running into are the `e2e` tests.
They actually start a server is fire a request and assert the response from the mocked target.
The e2e setup is a bit dated and might be harder to work with.


coveralls: 
[![Coverage Status](https://coveralls.io/builds/27632551/badge)](https://coveralls.io/builds/27632551)

Coverage increased (+0.09%) to 93.458% when pulling **33ebf4aacf1b23fc76e94f2954fa2f7d210f1b9c on LiranBri:feat/async-router** into **3b9730826187c708fbd16cb0baa588f6aad73a00 on chimurai:master**.

LiranBri: @chimurai tests added :)
chimurai: Awesome!

Could you update the [README.md](https://github.com/chimurai/http-proxy-middleware/blob/master/README.md) with a tiny async example. Just to indicate `router` support async functions.

```javascript
option.router

// Custom async router function
router: async function(req) {
    return 'http://localhost:8004';
}
```


 
LiranBri: @chimurai looks good?

Patch:
diff --git a/README.md b/README.md
index 35507eb..945802a 100644
--- a/README.md
+++ b/README.md
@@ -217,6 +217,12 @@ Providing an alternative way to decide which requests should be proxied; In case
   router: function(req) {
       return 'http://localhost:8004';
   }
+
+  // Asynchronous router function which returns promise
+  router: async function(req) {
+      const url = await doSomeIO();
+      return url;
+  }
   ```
 
 - **option.logLevel**: string, ['debug', 'info', 'warn', 'error', 'silent']. Default: `'info'`
diff --git a/src/http-proxy-middleware.ts b/src/http-proxy-middleware.ts
index 342e9da..f3f6634 100644
--- a/src/http-proxy-middleware.ts
+++ b/src/http-proxy-middleware.ts
@@ -47,7 +47,7 @@ export class HttpProxyMiddleware {
   // https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript#red-flags-for-this
   public middleware = async (req, res, next) => {
     if (this.shouldProxy(this.config.context, req)) {
-      const activeProxyOptions = this.prepareProxyRequest(req);
+      const activeProxyOptions = await this.prepareProxyRequest(req);
       this.proxy.web(req, res, activeProxyOptions);
     } else {
       next();
@@ -68,9 +68,9 @@ export class HttpProxyMiddleware {
     }
   };
 
-  private handleUpgrade = (req, socket, head) => {
+  private handleUpgrade = async (req, socket, head) => {
     if (this.shouldProxy(this.config.context, req)) {
-      const activeProxyOptions = this.prepareProxyRequest(req);
+      const activeProxyOptions = await this.prepareProxyRequest(req);
       this.proxy.ws(req, socket, head, activeProxyOptions);
       this.logger.info('[HPM] Upgrading to WebSocket');
     }
@@ -97,7 +97,7 @@ export class HttpProxyMiddleware {
    * @param {Object} req
    * @return {Object} proxy options
    */
-  private prepareProxyRequest = req => {
+  private prepareProxyRequest = async req => {
     // https://github.com/chimurai/http-proxy-middleware/issues/17
     // https://github.com/chimurai/http-proxy-middleware/issues/94
     req.url = req.originalUrl || req.url;
@@ -109,7 +109,7 @@ export class HttpProxyMiddleware {
     // Apply in order:
     // 1. option.router
     // 2. option.pathRewrite
-    this.applyRouter(req, newProxyOptions);
+    await this.applyRouter(req, newProxyOptions);
     this.applyPathRewrite(req, this.pathRewriter);
 
     // debug logging for both http(s) and websockets
@@ -133,11 +133,11 @@ export class HttpProxyMiddleware {
   };
 
   // Modify option.target when router present.
-  private applyRouter = (req, options) => {
+  private applyRouter = async (req, options) => {
     let newTarget;
 
     if (options.router) {
-      newTarget = Router.getTarget(req, options);
+      newTarget = await Router.getTarget(req, options);
 
       if (newTarget) {
         this.logger.debug(
diff --git a/src/router.ts b/src/router.ts
index 0e24a2e..37200b9 100644
--- a/src/router.ts
+++ b/src/router.ts
@@ -2,14 +2,14 @@ import * as _ from 'lodash';
 import { getInstance } from './logger';
 const logger = getInstance();
 
-export function getTarget(req, config) {
+export async function getTarget(req, config) {
   let newTarget;
   const router = config.router;
 
   if (_.isPlainObject(router)) {
     newTarget = getTargetFromProxyTable(req, router);
   } else if (_.isFunction(router)) {
-    newTarget = router(req);
+    newTarget = await router(req);
   }
 
   return newTarget;


Test patch:
diff --git a/test/unit/router.spec.ts b/test/unit/router.spec.ts
index 06f44e4..8cad56b 100644
--- a/test/unit/router.spec.ts
+++ b/test/unit/router.spec.ts
@@ -40,7 +40,33 @@ describe('router unit test', () => {
         expect(request.url).toBe('/');
       });
       it('should return new target', () => {
-        expect(result).toBe('http://foobar.com:666');
+        expect(result).resolves.toBe('http://foobar.com:666');
+      });
+    });
+  });
+
+  describe('router.getTarget from async function', () => {
+    let request;
+
+    beforeEach(() => {
+      proxyOptionWithRouter = {
+        target: 'http://localhost:6000',
+        async router(req) {
+          request = req;
+          return 'http://foobar.com:666';
+        }
+      };
+
+      result = getTarget(fakeReq, proxyOptionWithRouter);
+    });
+
+    describe('custom dynamic router async function', () => {
+      it('should provide the request object for dynamic routing', () => {
+        expect(request.headers.host).toBe('localhost');
+        expect(request.url).toBe('/');
+      });
+      it('should return new target', () => {
+        expect(result).resolves.toBe('http://foobar.com:666');
       });
     });
   });
@@ -64,7 +90,7 @@ describe('router unit test', () => {
     describe('without router config', () => {
       it('should return the normal target when router not present in config', () => {
         result = getTarget(fakeReq, config);
-        expect(result).toBeUndefined();
+        expect(result).resolves.toBeUndefined();
       });
     });
 
@@ -72,13 +98,13 @@ describe('router unit test', () => {
       it('should target http://localhost:6001 when for router:"alpha.localhost"', () => {
         fakeReq.headers.host = 'alpha.localhost';
         result = getTarget(fakeReq, proxyOptionWithRouter);
-        expect(result).toBe('http://localhost:6001');
+        expect(result).resolves.toBe('http://localhost:6001');
       });
 
       it('should target http://localhost:6002 when for router:"beta.localhost"', () => {
         fakeReq.headers.host = 'beta.localhost';
         result = getTarget(fakeReq, proxyOptionWithRouter);
-        expect(result).toBe('http://localhost:6002');
+        expect(result).resolves.toBe('http://localhost:6002');
       });
     });
 
@@ -86,21 +112,21 @@ describe('router unit test', () => {
       it('should target http://localhost:6004 without path', () => {
         fakeReq.headers.host = 'gamma.localhost';
         result = getTarget(fakeReq, proxyOptionWithRouter);
-        expect(result).toBe('http://localhost:6004');
+        expect(result).resolves.toBe('http://localhost:6004');
       });
 
       it('should target http://localhost:6003 exact path match', () => {
         fakeReq.headers.host = 'gamma.localhost';
         fakeReq.url = '/api';
         result = getTarget(fakeReq, proxyOptionWithRouter);
-        expect(result).toBe('http://localhost:6003');
+        expect(result).resolves.toBe('http://localhost:6003');
       });
 
       it('should target http://localhost:6004 when contains path', () => {
         fakeReq.headers.host = 'gamma.localhost';
         fakeReq.url = '/api/books/123';
         result = getTarget(fakeReq, proxyOptionWithRouter);
-        expect(result).toBe('http://localhost:6003');
+        expect(result).resolves.toBe('http://localhost:6003');
       });
     });
 
@@ -108,19 +134,19 @@ describe('router unit test', () => {
       it('should target http://localhost:6005 with just a path as router config', () => {
         fakeReq.url = '/rest';
         result = getTarget(fakeReq, proxyOptionWithRouter);
-        expect(result).toBe('http://localhost:6005');
+        expect(result).resolves.toBe('http://localhost:6005');
       });
 
       it('should target http://localhost:6005 with just a path as router config', () => {
         fakeReq.url = '/rest/deep/path';
         result = getTarget(fakeReq, proxyOptionWithRouter);
-        expect(result).toBe('http://localhost:6005');
+        expect(result).resolves.toBe('http://localhost:6005');
       });
 
       it('should target http://localhost:6000 path in not present in router config', () => {
         fakeReq.url = '/unknow-path';
         result = getTarget(fakeReq, proxyOptionWithRouter);
-        expect(result).toBeUndefined();
+        expect(result).resolves.toBeUndefined();
       });
     });
 
@@ -128,7 +154,7 @@ describe('router unit test', () => {
       it('should return first matching target when similar paths are configured', () => {
         fakeReq.url = '/some/specific/path';
         result = getTarget(fakeReq, proxyOptionWithRouter);
-        expect(result).toBe('http://localhost:6006');
+        expect(result).resolves.toBe('http://localhost:6006');
       });
     });
   });