Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions src/handlers/response-interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,14 @@ function decompress<TReq extends http.IncomingMessage = http.IncomingMessage>(
* Copy original headers
* https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L78
*/
function copyHeaders(originalResponse, response): void {
function copyHeaders<TRes extends http.ServerResponse = http.ServerResponse>(
originalResponse: http.IncomingMessage,
response: TRes,
): void {
debug('copy original response headers');

response.statusCode = originalResponse.statusCode;
response.statusMessage = originalResponse.statusMessage;
response.statusCode = originalResponse.statusCode as number;
response.statusMessage = originalResponse.statusMessage as string;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

if (response.setHeader) {
let keys = Object.keys(originalResponse.headers);
Expand All @@ -117,13 +120,15 @@ function copyHeaders(originalResponse, response): void {

if (key === 'set-cookie') {
// remove cookie domain
value = Array.isArray(value) ? value : [value];
value = (Array.isArray(value) ? value : [value]) as string[];
value = value.map((x) => x.replace(/Domain=[^;]+?/i, ''));
}

response.setHeader(key, value);
response.setHeader(key, value as readonly string[]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
});
} else {
response.headers = originalResponse.headers;
if ('headers' in response) {
response.headers = originalResponse.headers;
}
}
}
21 changes: 12 additions & 9 deletions src/http-proxy-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { Debug as debug } from './debug.js';
import { getPlugins } from './get-plugins.js';
import { getLogger } from './logger.js';
import { matchPathFilter } from './path-filter.js';
import * as PathRewriter from './path-rewriter.js';
import * as Router from './router.js';
import { createPathRewriter } from './path-rewriter.js';
import { getTarget } from './router.js';
import type { Filter, Logger, Options, RequestHandler } from './types.js';
import { getFunctionName } from './utils/function.js';

Expand All @@ -22,7 +22,7 @@ export class HttpProxyMiddleware<
private serverOnCloseSubscribed = false;
private proxyOptions: Options<TReq, TRes>;
private proxy: ProxyServer<TReq, TRes>;
private pathRewriter;
private pathRewriter: ReturnType<typeof createPathRewriter<TReq>>;
private logger: Logger;

constructor(options: Options<TReq, TRes>) {
Expand All @@ -35,7 +35,7 @@ export class HttpProxyMiddleware<

this.registerPlugins(this.proxy, this.proxyOptions);

this.pathRewriter = PathRewriter.createPathRewriter(this.proxyOptions.pathRewrite); // returns undefined when "pathRewrite" is not provided
this.pathRewriter = createPathRewriter(this.proxyOptions.pathRewrite); // returns undefined when "pathRewrite" is not provided

// https://github.com/chimurai/http-proxy-middleware/issues/19
// expose function to upgrade externally
Expand Down Expand Up @@ -181,7 +181,7 @@ export class HttpProxyMiddleware<
* @param {Object} req
* @return {Object} proxy options
*/
private prepareProxyRequest = async (req: http.IncomingMessage) => {
private prepareProxyRequest = async (req: TReq) => {
const newProxyOptions = Object.assign({}, this.proxyOptions);

// Apply in order:
Expand All @@ -194,11 +194,11 @@ export class HttpProxyMiddleware<
};

// Modify option.target when router present.
private applyRouter = async (req: http.IncomingMessage, options: Options<TReq, TRes>) => {
private applyRouter = async (req: TReq, options: Options<TReq, TRes>) => {
let newTarget;

if (options.router) {
newTarget = await Router.getTarget(req, options);
newTarget = await getTarget(req, options);

if (newTarget) {
debug('router new target: "%s"', newTarget);
Expand All @@ -208,9 +208,12 @@ export class HttpProxyMiddleware<
};

// rewrite path
private applyPathRewrite = async (req: http.IncomingMessage, pathRewriter) => {
private applyPathRewrite = async (
req: TReq,
pathRewriter: ReturnType<typeof createPathRewriter<TReq>>,
) => {
if (pathRewriter) {
const path = await pathRewriter(req.url, req);
const path = await pathRewriter(req.url as string, req);

if (typeof path === 'string') {
debug('pathRewrite new path: %s', path);
Expand Down
18 changes: 11 additions & 7 deletions src/path-rewriter.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import type { IncomingMessage } from 'node:http';

import isPlainObject from 'is-plain-obj';

import { Debug } from './debug.js';
import { ERRORS } from './errors.js';
import type { PathRewriteConfig } from './types.js';

const debug = Debug.extend('path-rewriter');

type RewriteRule = { regex: RegExp; value: string };

/**
* Create rewrite function, to cache parsed rewrite rules.
*
* @param {Object} rewriteConfig
* @return {Function} Function to rewrite paths; This function should accept `path` (request.url) as parameter
*/
export function createPathRewriter(rewriteConfig) {
export function createPathRewriter<TReq extends IncomingMessage = IncomingMessage>(
rewriteConfig: PathRewriteConfig<TReq> | undefined,
) {
let rulesCache: RewriteRule[];

if (!isValidRewriteConfig(rewriteConfig)) {
Expand All @@ -28,7 +30,7 @@ export function createPathRewriter(rewriteConfig) {
return rewritePath;
}

function rewritePath(path) {
function rewritePath(path: string) {
let result = path;

for (const rule of rulesCache) {
Expand All @@ -43,7 +45,9 @@ export function createPathRewriter(rewriteConfig) {
}
}

function isValidRewriteConfig(rewriteConfig) {
function isValidRewriteConfig<TReq extends IncomingMessage = IncomingMessage>(
rewriteConfig: PathRewriteConfig<TReq> | undefined,
): boolean {
if (typeof rewriteConfig === 'function') {
return true;
} else if (isPlainObject(rewriteConfig)) {
Expand All @@ -55,7 +59,7 @@ function isValidRewriteConfig(rewriteConfig) {
}
}

function parsePathRewriteRules(rewriteConfig: Record<string, string>) {
function parsePathRewriteRules(rewriteConfig: PathRewriteConfig | undefined) {
const rules: RewriteRule[] = [];

if (isPlainObject(rewriteConfig)) {
Expand Down
20 changes: 15 additions & 5 deletions src/router.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import type * as http from 'node:http';

import type { ProxyServerOptions } from 'httpxy';
import isPlainObject from 'is-plain-obj';

import { Debug } from './debug.js';
import type { Options } from './index.js';

const debug = Debug.extend('router');

export async function getTarget(req, config) {
export async function getTarget<
TReq extends http.IncomingMessage = http.IncomingMessage,
TRes extends http.ServerResponse = http.ServerResponse,
>(req: TReq, config: Options<TReq, TRes>) {
let newTarget;
const router = config.router;

Expand All @@ -17,10 +24,13 @@ export async function getTarget(req, config) {
return newTarget;
}

function getTargetFromProxyTable(req, table) {
function getTargetFromProxyTable<TReq extends http.IncomingMessage>(
req: TReq,
table: Record<string, ProxyServerOptions['target']>,
) {
let result;
const host = req.headers.host;
const path = req.url;
const host = req.headers.host ?? '';
const path = req.url ?? '';

const hostAndPath = host + path;

Expand All @@ -45,6 +55,6 @@ function getTargetFromProxyTable(req, table) {
return result;
}

function containsPath(v) {
function containsPath(v: string) {
return v.indexOf('/') > -1;
}
13 changes: 8 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ export interface OnProxyEvent<

export type Logger = Pick<Console, 'info' | 'warn' | 'error'>;

export type PathRewriteConfig<TReq extends http.IncomingMessage = http.IncomingMessage> =
| { [regexp: string]: string }
| ((path: string, req: TReq) => string | undefined)
| ((path: string, req: TReq) => Promise<string>);

export interface Options<
TReq extends http.IncomingMessage = http.IncomingMessage,
TRes extends http.ServerResponse = http.ServerResponse,
Expand All @@ -82,10 +87,8 @@ export interface Options<
* ```
* @link https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/pathRewrite.md
*/
pathRewrite?:
| { [regexp: string]: string }
| ((path: string, req: TReq) => string | undefined)
| ((path: string, req: TReq) => Promise<string>);
pathRewrite?: PathRewriteConfig<TReq>;

/**
* Access the internal http-proxy server instance to customize behavior
*
Expand Down Expand Up @@ -141,7 +144,7 @@ export interface Options<
* @link https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/router.md
*/
router?:
| { [hostOrPath: string]: ProxyServerOptions['target'] }
| Record<string, ProxyServerOptions['target']>
| ((req: TReq) => ProxyServerOptions['target'])
| ((req: TReq) => Promise<ProxyServerOptions['target']>);
/**
Expand Down
5 changes: 3 additions & 2 deletions test/e2e/express-error-middleware.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type express from 'express';
import request from 'supertest';
import { describe, expect, it } from 'vitest';

Expand All @@ -12,12 +13,12 @@ describe('express error middleware', () => {
router: (req) => undefined, // Trigger "Error: Must provide a proper URL as target"
});

const errorMiddleware = (err, req, res, next) => {
const errorMiddleware: express.ErrorRequestHandler = (err, req, res, next) => {
httpProxyError = err;
res.status(504).send('Something broke!');
};

const app = createApp(proxyMiddleware, errorMiddleware);
const app = createApp(proxyMiddleware, errorMiddleware as any);
const response = await request(app).get('/get').expect(504);

expect(httpProxyError?.message).toBe('Must provide a proper URL as target');
Expand Down
10 changes: 5 additions & 5 deletions test/e2e/express-router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import express from 'express';
import request from 'supertest';
import { beforeEach, describe, expect, it } from 'vitest';

import type { Options } from '../../src/index.js';
import type { Filter, Options } from '../../src/index.js';
import { createProxyMiddleware } from './test-kit.js';

describe('Usage in Express', () => {
Expand All @@ -19,11 +19,11 @@ describe('Usage in Express', () => {
// sub route config
const sub = express.Router();

function filter(pathname, req) {
const filter: Filter = (pathname, req) => {
const urlFilter = new RegExp('^/sub/api');
const match = urlFilter.test(pathname);
return match;
}
};

/**
* Mount proxy without 'path' in sub route
Expand All @@ -50,8 +50,8 @@ describe('Usage in Express', () => {
});
});

function jsonMiddleware(data) {
return (req, res) => {
function jsonMiddleware(data: Record<string, unknown>) {
return (req: express.Request, res: express.Response) => {
res.json(data);
};
}
Expand Down
8 changes: 4 additions & 4 deletions test/e2e/http-proxy-middleware.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getLocal } from 'mockttp';
import request from 'supertest';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import type { Logger } from '../../src/types.js';
import type { Filter, Logger } from '../../src/types.js';
import { createApp, createAppWithPath, createProxyMiddleware, fixRequestBody } from './test-kit.js';

describe('E2E http-proxy-middleware', () => {
Expand Down Expand Up @@ -135,7 +135,7 @@ describe('E2E http-proxy-middleware', () => {

describe('custom pathFilter matcher/filter', () => {
it('should have response body: "HELLO WEB"', async () => {
const filter = (path, req) => {
const filter: Filter = (path, req) => {
return true;
};

Expand All @@ -154,7 +154,7 @@ describe('E2E http-proxy-middleware', () => {
});

it('should not proxy when filter returns false', async () => {
const filter = (path, req) => {
const filter: Filter = (path, req) => {
return false;
};

Expand All @@ -174,7 +174,7 @@ describe('E2E http-proxy-middleware', () => {

it('should not proxy when filter throws Error', async () => {
const myError = new Error('MY_ERROR');
const filter = (path, req) => {
const filter: Filter = (path, req) => {
throw myError;
};

Expand Down
2 changes: 1 addition & 1 deletion test/e2e/router.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ describe('E2E router', () => {
});

describe('router with proxyTable', () => {
let agent;
let agent: request.Agent;

beforeEach(() => {
const app = createAppWithPath(
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/test-kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export {
fixRequestBody,
} from '../../src/index.js';

export function createApp(...middlewares): Express {
export function createApp(...middlewares: RequestHandler[]): Express {
Comment thread
chimurai marked this conversation as resolved.
const app = express();
app.use(...middlewares);
return app;
Expand Down
12 changes: 10 additions & 2 deletions test/types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,12 @@ describe('http-proxy-middleware TypeScript Types', () => {
});

it('should have router Type with function', () => {
options = { router: (path) => '/path' };
options = { router: (req) => '/path' };
expect(options).toBeDefined();
});

it('should have router Type with async function', () => {
options = { router: async (path) => '/path' };
options = { router: async (req) => '/path' };
expect(options).toBeDefined();
});
});
Expand Down Expand Up @@ -183,6 +183,14 @@ describe('http-proxy-middleware TypeScript Types', () => {
middleware({
router: (req) => req.params,
pathFilter: (pathname, req) => !!req.params,
pathRewrite: (path, req) => {
req.params;

// @ts-expect-error: should error when request is typed as `any`
req.unknownProperty;

return path;
},
on: {
error(error, req, res, target) {
req.params;
Expand Down
Loading
Loading