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

feat: async router #379

Merged
merged 5 commits into from Dec 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -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'`
Expand Down
14 changes: 7 additions & 7 deletions src/http-proxy-middleware.ts
Expand Up @@ -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();
Expand All @@ -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');
}
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions src/router.ts
Expand Up @@ -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;
Expand Down
48 changes: 37 additions & 11 deletions test/unit/router.spec.ts
Expand Up @@ -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');
});
});
});
Expand All @@ -64,71 +90,71 @@ 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();
});
});

describe('with just the host in router config', () => {
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');
});
});

describe('with host and host + path config', () => {
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');
});
});

describe('with just the path', () => {
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();
});
});

describe('matching order of router config', () => {
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');
});
});
});
Expand Down