Skip to content

Commit 0ef0be6

Browse files
authored
feat(typescript): type improvements (#882)
* feat(types): improve types * refactor(types): remove internal Request and Response type * feat(types): proxy handlers generic types * refactor(handlers): use generic types in fixRequestBody and responseInterceptor * refactor(package): remove optional @types/express * test(types): use type narrowing * feat(types): support contextual typing from server
1 parent fd4e558 commit 0ef0be6

23 files changed

+407
-127
lines changed

.eslintrc.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,11 @@ module.exports = {
2020
'@typescript-eslint/no-var-requires': 'off',
2121
},
2222
},
23+
{
24+
files: ['src/**/*.ts'],
25+
rules: {
26+
'no-restricted-imports': ['error', { paths: ['express'] }],
27+
},
28+
},
2329
],
2430
};

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## next
44

5+
- feat(typescript): type improvements ([#882](https://github.com/chimurai/http-proxy-middleware/pull/882))
56
- chore(deps): update micromatch to 4.0.5
67
- chore(package): bump devDependencies
78
- feat(legacyCreateProxyMiddleware): show migration tips ([#756](https://github.com/chimurai/http-proxy-middleware/pull/756))

README.md

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,12 @@ const { createProxyMiddleware } = require('http-proxy-middleware');
141141

142142
const app = express();
143143

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

154151
// mount `exampleProxy` in web server
155152
app.use('/api', exampleProxy);

package.json

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,21 +83,13 @@
8383
"ws": "8.10.0"
8484
},
8585
"dependencies": {
86-
"@types/http-proxy": "^1.17.8",
86+
"@types/http-proxy": "^1.17.10",
8787
"debug": "^4.3.4",
8888
"http-proxy": "^1.18.1",
8989
"is-glob": "^4.0.1",
9090
"is-plain-obj": "^3.0.0",
9191
"micromatch": "^4.0.5"
9292
},
93-
"peerDependencies": {
94-
"@types/express": "^4.17.13"
95-
},
96-
"peerDependenciesMeta": {
97-
"@types/express": {
98-
"optional": true
99-
}
100-
},
10193
"engines": {
10294
"node": ">=12.0.0"
10395
},

recipes/servers.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Next project: `/pages/api/users.ts`
7272
import type { NextApiRequest, NextApiResponse } from 'next';
7373
import { createProxyMiddleware } from 'http-proxy-middleware';
7474

75-
const proxyMiddleware = createProxyMiddleware({
75+
const proxyMiddleware = createProxyMiddleware<NextApiRequest, NextApiResponse>({
7676
target: 'http://jsonplaceholder.typicode.com',
7777
changeOrigin: true,
7878
pathRewrite: {

src/configuration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ERRORS } from './errors';
22
import { Options } from './types';
33

4-
export function verifyConfig(options: Options): void {
4+
export function verifyConfig<TReq, TRes>(options: Options<TReq, TRes>): void {
55
if (!options.target && !options.router) {
66
throw new Error(ERRORS.ERR_CONFIG_FACTORY_TARGET_MISSING);
77
}

src/get-plugins.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import {
66
proxyEventsPlugin,
77
} from './plugins/default';
88

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

13-
const defaultPlugins: Plugin[] = !!options.ejectPlugins
13+
const defaultPlugins = !!options.ejectPlugins
1414
? [] // no default plugins when ejecting
1515
: [debugProxyErrorsPlugin, proxyEventsPlugin, loggerPlugin, ...maybeErrorResponsePlugin];
16-
const userPlugins: Plugin[] = options.plugins ?? [];
17-
return [...defaultPlugins, ...userPlugins];
16+
const userPlugins: Plugin<TReq, TRes>[] = options.plugins ?? [];
17+
return [...defaultPlugins, ...userPlugins] as unknown as Plugin<TReq, TRes>[];
1818
}

src/handlers/fix-request-body.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import type * as http from 'http';
2-
import type * as express from 'express';
3-
import type { Request } from '../types';
42
import * as querystring from 'querystring';
53

4+
export type BodyParserLikeRequest = http.IncomingMessage & { body: any };
5+
66
/**
77
* Fix proxied body if bodyParser is involved.
88
*/
9-
export function fixRequestBody(proxyReq: http.ClientRequest, req: Request): void {
10-
const requestBody = (req as Request<express.Request>).body;
9+
export function fixRequestBody<TReq = http.IncomingMessage>(
10+
proxyReq: http.ClientRequest,
11+
req: TReq
12+
): void {
13+
const requestBody = (req as unknown as BodyParserLikeRequest).body;
1114

1215
if (!requestBody) {
1316
return;

src/handlers/response-interceptor.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { getFunctionName } from '../utils/function';
55

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

8-
type Interceptor = (
8+
type Interceptor<TReq = http.IncomingMessage, TRes = http.ServerResponse> = (
99
buffer: Buffer,
10-
proxyRes: http.IncomingMessage,
11-
req: http.IncomingMessage,
12-
res: http.ServerResponse
10+
proxyRes: TReq,
11+
req: TReq,
12+
res: TRes
1313
) => Promise<Buffer | string>;
1414

1515
/**
@@ -19,18 +19,21 @@ type Interceptor = (
1919
*
2020
* NOTE: must set options.selfHandleResponse=true (prevent automatic call of res.end())
2121
*/
22-
export function responseInterceptor(interceptor: Interceptor) {
22+
export function responseInterceptor<
23+
TReq extends http.IncomingMessage = http.IncomingMessage,
24+
TRes extends http.ServerResponse = http.ServerResponse
25+
>(interceptor: Interceptor<TReq, TRes>) {
2326
return async function proxyResResponseInterceptor(
24-
proxyRes: http.IncomingMessage,
25-
req: http.IncomingMessage,
26-
res: http.ServerResponse
27+
proxyRes: TReq,
28+
req: TReq,
29+
res: TRes
2730
): Promise<void> {
2831
debug('intercept proxy response');
2932
const originalProxyRes = proxyRes;
3033
let buffer = Buffer.from('', 'utf8');
3134

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

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

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

99105
response.statusCode = originalResponse.statusCode;

src/http-proxy-middleware.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import type * as http from 'http';
12
import type * as https from 'https';
2-
import type { Request, RequestHandler, Options, Filter } from './types';
3+
import type { RequestHandler, Options, Filter } from './types';
34
import * as httpProxy from 'http-proxy';
45
import { verifyConfig } from './configuration';
56
import { getPlugins } from './get-plugins';
@@ -9,15 +10,15 @@ import * as Router from './router';
910
import { Debug as debug } from './debug';
1011
import { getFunctionName } from './utils/function';
1112

12-
export class HttpProxyMiddleware {
13+
export class HttpProxyMiddleware<TReq, TRes> {
1314
private wsInternalSubscribed = false;
1415
private serverOnCloseSubscribed = false;
15-
private proxyOptions: Options;
16-
private proxy: httpProxy;
16+
private proxyOptions: Options<TReq, TRes>;
17+
private proxy: httpProxy<TReq, TRes>;
1718
private pathRewriter;
1819

19-
constructor(options: Options) {
20-
verifyConfig(options);
20+
constructor(options: Options<TReq, TRes>) {
21+
verifyConfig<TReq, TRes>(options);
2122
this.proxyOptions = options;
2223

2324
debug(`create proxy server`);
@@ -74,8 +75,8 @@ export class HttpProxyMiddleware {
7475
}
7576
};
7677

77-
private registerPlugins(proxy: httpProxy, options: Options) {
78-
const plugins = getPlugins(options);
78+
private registerPlugins(proxy: httpProxy<TReq, TRes>, options: Options<TReq, TRes>) {
79+
const plugins = getPlugins<TReq, TRes>(options);
7980
plugins.forEach((plugin) => {
8081
debug(`register plugin: "${getFunctionName(plugin)}"`);
8182
plugin(proxy, options);
@@ -92,7 +93,7 @@ export class HttpProxyMiddleware {
9293
}
9394
};
9495

95-
private handleUpgrade = async (req: Request, socket, head) => {
96+
private handleUpgrade = async (req: http.IncomingMessage, socket, head) => {
9697
if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
9798
const activeProxyOptions = await this.prepareProxyRequest(req);
9899
this.proxy.ws(req, socket, head, activeProxyOptions);
@@ -103,7 +104,7 @@ export class HttpProxyMiddleware {
103104
/**
104105
* Determine whether request should be proxied.
105106
*/
106-
private shouldProxy = (pathFilter: Filter, req: Request): boolean => {
107+
private shouldProxy = (pathFilter: Filter<TReq>, req: http.IncomingMessage): boolean => {
107108
return matchPathFilter(pathFilter, req.url, req);
108109
};
109110

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

139140
// Modify option.target when router present.
140-
private applyRouter = async (req: Request, options) => {
141+
private applyRouter = async (req: http.IncomingMessage, options) => {
141142
let newTarget;
142143

143144
if (options.router) {
@@ -151,7 +152,7 @@ export class HttpProxyMiddleware {
151152
};
152153

153154
// rewrite path
154-
private applyPathRewrite = async (req: Request, pathRewriter) => {
155+
private applyPathRewrite = async (req: http.IncomingMessage, pathRewriter) => {
155156
if (pathRewriter) {
156157
const path = await pathRewriter(req.url, req);
157158

0 commit comments

Comments
 (0)