Skip to content

feat(typescript): type improvements #882

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

Merged
merged 7 commits into from
Mar 3, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,11 @@ module.exports = {
'@typescript-eslint/no-var-requires': 'off',
},
},
{
files: ['src/**/*.ts'],
rules: {
'no-restricted-imports': ['error', { paths: ['express'] }],
},
},
],
};
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## next

- feat(typescript): type improvements ([#882](https://github.com/chimurai/http-proxy-middleware/pull/882))
- chore(deps): update micromatch to 4.0.5
- chore(package): bump devDependencies
- feat(legacyCreateProxyMiddleware): show migration tips ([#756](https://github.com/chimurai/http-proxy-middleware/pull/756))
Expand Down
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,12 @@ const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

// proxy middleware options
/** @type {import('http-proxy-middleware/dist/types').Options} */
const options = {
// create the proxy
/** @type {import('http-proxy-middleware/dist/types').RequestHandler<express.Request, express.Response>} */
const exampleProxy = createProxyMiddleware({
target: 'http://www.example.org/api', // target host with the same base path
changeOrigin: true, // needed for virtual hosted sites
};

// create the proxy
const exampleProxy = createProxyMiddleware(options);
});

// mount `exampleProxy` in web server
app.use('/api', exampleProxy);
Expand Down
10 changes: 1 addition & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,21 +83,13 @@
"ws": "8.10.0"
},
"dependencies": {
"@types/http-proxy": "^1.17.8",
"@types/http-proxy": "^1.17.10",
"debug": "^4.3.4",
"http-proxy": "^1.18.1",
"is-glob": "^4.0.1",
"is-plain-obj": "^3.0.0",
"micromatch": "^4.0.5"
},
"peerDependencies": {
"@types/express": "^4.17.13"
},
"peerDependenciesMeta": {
"@types/express": {
"optional": true
}
},
"engines": {
"node": ">=12.0.0"
},
Expand Down
2 changes: 1 addition & 1 deletion recipes/servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Next project: `/pages/api/users.ts`
import type { NextApiRequest, NextApiResponse } from 'next';
import { createProxyMiddleware } from 'http-proxy-middleware';

const proxyMiddleware = createProxyMiddleware({
const proxyMiddleware = createProxyMiddleware<NextApiRequest, NextApiResponse>({
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
pathRewrite: {
Expand Down
2 changes: 1 addition & 1 deletion src/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ERRORS } from './errors';
import { Options } from './types';

export function verifyConfig(options: Options): void {
export function verifyConfig<TReq, TRes>(options: Options<TReq, TRes>): void {
if (!options.target && !options.router) {
throw new Error(ERRORS.ERR_CONFIG_FACTORY_TARGET_MISSING);
}
Expand Down
8 changes: 4 additions & 4 deletions src/get-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import {
proxyEventsPlugin,
} from './plugins/default';

export function getPlugins(options: Options): Plugin[] {
export function getPlugins<TReq, TRes>(options: Options<TReq, TRes>): Plugin<TReq, TRes>[] {
// don't load default errorResponsePlugin if user has specified their own
const maybeErrorResponsePlugin = !!options.on?.error ? [] : [errorResponsePlugin];

const defaultPlugins: Plugin[] = !!options.ejectPlugins
const defaultPlugins = !!options.ejectPlugins
? [] // no default plugins when ejecting
: [debugProxyErrorsPlugin, proxyEventsPlugin, loggerPlugin, ...maybeErrorResponsePlugin];
const userPlugins: Plugin[] = options.plugins ?? [];
return [...defaultPlugins, ...userPlugins];
const userPlugins: Plugin<TReq, TRes>[] = options.plugins ?? [];
return [...defaultPlugins, ...userPlugins] as unknown as Plugin<TReq, TRes>[];
}
11 changes: 7 additions & 4 deletions src/handlers/fix-request-body.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import type * as http from 'http';
import type * as express from 'express';
import type { Request } from '../types';
import * as querystring from 'querystring';

export type BodyParserLikeRequest = http.IncomingMessage & { body: any };

/**
* Fix proxied body if bodyParser is involved.
*/
export function fixRequestBody(proxyReq: http.ClientRequest, req: Request): void {
const requestBody = (req as Request<express.Request>).body;
export function fixRequestBody<TReq = http.IncomingMessage>(
proxyReq: http.ClientRequest,
req: TReq
): void {
const requestBody = (req as unknown as BodyParserLikeRequest).body;

if (!requestBody) {
return;
Expand Down
28 changes: 17 additions & 11 deletions src/handlers/response-interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { getFunctionName } from '../utils/function';

const debug = Debug.extend('response-interceptor');

type Interceptor = (
type Interceptor<TReq = http.IncomingMessage, TRes = http.ServerResponse> = (
buffer: Buffer,
proxyRes: http.IncomingMessage,
req: http.IncomingMessage,
res: http.ServerResponse
proxyRes: TReq,
req: TReq,
res: TRes
) => Promise<Buffer | string>;

/**
Expand All @@ -19,18 +19,21 @@ type Interceptor = (
*
* NOTE: must set options.selfHandleResponse=true (prevent automatic call of res.end())
*/
export function responseInterceptor(interceptor: Interceptor) {
export function responseInterceptor<
TReq extends http.IncomingMessage = http.IncomingMessage,
TRes extends http.ServerResponse = http.ServerResponse
>(interceptor: Interceptor<TReq, TRes>) {
return async function proxyResResponseInterceptor(
proxyRes: http.IncomingMessage,
req: http.IncomingMessage,
res: http.ServerResponse
proxyRes: TReq,
req: TReq,
res: TRes
): Promise<void> {
debug('intercept proxy response');
const originalProxyRes = proxyRes;
let buffer = Buffer.from('', 'utf8');

// decompress proxy response
const _proxyRes = decompress(proxyRes, proxyRes.headers['content-encoding']);
const _proxyRes = decompress<TReq>(proxyRes, proxyRes.headers['content-encoding']);

// concat data stream
_proxyRes.on('data', (chunk) => (buffer = Buffer.concat([buffer, chunk])));
Expand Down Expand Up @@ -62,7 +65,10 @@ export function responseInterceptor(interceptor: Interceptor) {
* Streaming decompression of proxy response
* source: https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L116
*/
function decompress(proxyRes: http.IncomingMessage, contentEncoding: string) {
function decompress<TReq extends http.IncomingMessage = http.IncomingMessage>(
proxyRes: TReq,
contentEncoding: string
): TReq {
let _proxyRes = proxyRes;
let decompress;

Expand Down Expand Up @@ -93,7 +99,7 @@ function decompress(proxyRes: http.IncomingMessage, contentEncoding: string) {
* Copy original headers
* https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L78
*/
function copyHeaders(originalResponse, response) {
function copyHeaders(originalResponse, response): void {
debug('copy original response headers');

response.statusCode = originalResponse.statusCode;
Expand Down
27 changes: 14 additions & 13 deletions src/http-proxy-middleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type * as http from 'http';
import type * as https from 'https';
import type { Request, RequestHandler, Options, Filter } from './types';
import type { RequestHandler, Options, Filter } from './types';
import * as httpProxy from 'http-proxy';
import { verifyConfig } from './configuration';
import { getPlugins } from './get-plugins';
Expand All @@ -9,15 +10,15 @@ import * as Router from './router';
import { Debug as debug } from './debug';
import { getFunctionName } from './utils/function';

export class HttpProxyMiddleware {
export class HttpProxyMiddleware<TReq, TRes> {
private wsInternalSubscribed = false;
private serverOnCloseSubscribed = false;
private proxyOptions: Options;
private proxy: httpProxy;
private proxyOptions: Options<TReq, TRes>;
private proxy: httpProxy<TReq, TRes>;
private pathRewriter;

constructor(options: Options) {
verifyConfig(options);
constructor(options: Options<TReq, TRes>) {
verifyConfig<TReq, TRes>(options);
this.proxyOptions = options;

debug(`create proxy server`);
Expand Down Expand Up @@ -74,8 +75,8 @@ export class HttpProxyMiddleware {
}
};

private registerPlugins(proxy: httpProxy, options: Options) {
const plugins = getPlugins(options);
private registerPlugins(proxy: httpProxy<TReq, TRes>, options: Options<TReq, TRes>) {
const plugins = getPlugins<TReq, TRes>(options);
plugins.forEach((plugin) => {
debug(`register plugin: "${getFunctionName(plugin)}"`);
plugin(proxy, options);
Expand All @@ -92,7 +93,7 @@ export class HttpProxyMiddleware {
}
};

private handleUpgrade = async (req: Request, socket, head) => {
private handleUpgrade = async (req: http.IncomingMessage, socket, head) => {
if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
const activeProxyOptions = await this.prepareProxyRequest(req);
this.proxy.ws(req, socket, head, activeProxyOptions);
Expand All @@ -103,7 +104,7 @@ export class HttpProxyMiddleware {
/**
* Determine whether request should be proxied.
*/
private shouldProxy = (pathFilter: Filter, req: Request): boolean => {
private shouldProxy = (pathFilter: Filter<TReq>, req: http.IncomingMessage): boolean => {
return matchPathFilter(pathFilter, req.url, req);
};

Expand All @@ -115,7 +116,7 @@ export class HttpProxyMiddleware {
* @param {Object} req
* @return {Object} proxy options
*/
private prepareProxyRequest = async (req: Request) => {
private prepareProxyRequest = async (req: http.IncomingMessage) => {
/**
* Incorrect usage confirmed: https://github.com/expressjs/express/issues/4854#issuecomment-1066171160
* Temporary restore req.url patch for {@link src/legacy/create-proxy-middleware.ts legacyCreateProxyMiddleware()}
Expand All @@ -137,7 +138,7 @@ export class HttpProxyMiddleware {
};

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

if (options.router) {
Expand All @@ -151,7 +152,7 @@ export class HttpProxyMiddleware {
};

// rewrite path
private applyPathRewrite = async (req: Request, pathRewriter) => {
private applyPathRewrite = async (req: http.IncomingMessage, pathRewriter) => {
if (pathRewriter) {
const path = await pathRewriter(req.url, req);

Expand Down
13 changes: 9 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { HttpProxyMiddleware } from './http-proxy-middleware';
import type { Options, RequestHandler } from './types';
import type { Options, RequestHandler, NextFunction } from './types';
import type * as http from 'http';

export function createProxyMiddleware(options: Options): RequestHandler {
const { middleware } = new HttpProxyMiddleware(options);
return middleware;
export function createProxyMiddleware<
TReq = http.IncomingMessage,
TRes = http.ServerResponse,
TNext = NextFunction
>(options: Options<TReq, TRes>): RequestHandler<TReq, TRes, TNext> {
const { middleware } = new HttpProxyMiddleware<TReq, TRes>(options);
return middleware as unknown as RequestHandler<TReq, TRes, TNext>;
}

export * from './handlers';
Expand Down
31 changes: 22 additions & 9 deletions src/legacy/create-proxy-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Debug } from '../debug';
import { Filter, RequestHandler } from '../types';
import { legacyOptionsAdapter } from './options-adapter';
import { LegacyOptions } from './types';
import type * as http from 'http';

const debug = Debug.extend('legacy-create-proxy-middleware');

Expand All @@ -12,18 +13,30 @@ const debug = Debug.extend('legacy-create-proxy-middleware');
*
* Use {@link createProxyMiddleware} instead.
*/
export function legacyCreateProxyMiddleware(shortHand: string): RequestHandler;
export function legacyCreateProxyMiddleware(legacyOptions: LegacyOptions): RequestHandler;
export function legacyCreateProxyMiddleware(
legacyContext: Filter,
legacyOptions: LegacyOptions
): RequestHandler;
export function legacyCreateProxyMiddleware(legacyContext, legacyOptions?): RequestHandler {
export function legacyCreateProxyMiddleware<
TReq = http.IncomingMessage,
TRes = http.ServerResponse
>(shortHand: string): RequestHandler<TReq, TRes>;
export function legacyCreateProxyMiddleware<
TReq = http.IncomingMessage,
TRes = http.ServerResponse
>(legacyOptions: LegacyOptions<TReq, TRes>): RequestHandler<TReq, TRes>;
export function legacyCreateProxyMiddleware<
TReq = http.IncomingMessage,
TRes = http.ServerResponse
>(
legacyContext: Filter<TReq>,
legacyOptions: LegacyOptions<TReq, TRes>
): RequestHandler<TReq, TRes>;
export function legacyCreateProxyMiddleware<
TReq = http.IncomingMessage,
TRes = http.ServerResponse
>(legacyContext, legacyOptions?): RequestHandler<TReq, TRes> {
debug('init');

const options = legacyOptionsAdapter(legacyContext, legacyOptions);
const options = legacyOptionsAdapter<TReq, TRes>(legacyContext, legacyOptions);

const proxyMiddleware = createProxyMiddleware(options);
const proxyMiddleware = createProxyMiddleware<TReq, TRes>(options);

// https://github.com/chimurai/http-proxy-middleware/pull/731/files#diff-07e6ad10bda0df091b737caed42767657cd0bd74a01246a1a0b7ab59c0f6e977L118
debug('add marker for patching req.url (old behavior)');
Expand Down
14 changes: 7 additions & 7 deletions src/legacy/options-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ const proxyEventMap = {
/**
* Convert {@link LegacyOptions legacy Options} to new {@link Options}
*/
export function legacyOptionsAdapter(
legacyContext: Filter | LegacyOptions,
legacyOptions: LegacyOptions
): Options {
let options: LegacyOptions;
export function legacyOptionsAdapter<TReq, TRes>(
legacyContext: Filter<TReq> | LegacyOptions<TReq, TRes>,
legacyOptions: LegacyOptions<TReq, TRes>
): Options<TReq, TRes> {
let options: LegacyOptions<TReq, TRes>;
let logger: Logger;

// https://github.com/chimurai/http-proxy-middleware/pull/716
Expand All @@ -39,7 +39,7 @@ export function legacyOptionsAdapter(
// https://github.com/chimurai/http-proxy-middleware/pull/722/files#diff-a2a171449d862fe29692ce031981047d7ab755ae7f84c707aef80701b3ea0c80L4
if (legacyContext && legacyOptions) {
debug('map legacy context/filter to options.pathFilter');
options = { ...legacyOptions, pathFilter: legacyContext as Filter };
options = { ...legacyOptions, pathFilter: legacyContext as Filter<TReq> };
logger = getLegacyLogger(options);

logger.warn(
Expand All @@ -53,7 +53,7 @@ export function legacyOptionsAdapter(
`
);
} else if (legacyContext && !legacyOptions) {
options = { ...(legacyContext as Options) };
options = { ...(legacyContext as LegacyOptions<TReq, TRes>) };
logger = getLegacyLogger(options);
}

Expand Down
5 changes: 3 additions & 2 deletions src/legacy/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// import * as httpProxy from 'http-proxy';
import type * as http from 'http';
import { Options } from '..';

/**
* @deprecated
*
* Will be removed in a future version.
*/
export interface LegacyOptions extends Options {
export interface LegacyOptions<TReq = http.IncomingMessage, TRes = http.ServerResponse>
extends Options<TReq, TRes> {
/**
* @deprecated
* Use `on.error` instead.
Expand Down
Loading