http-proxy-middleware 3.0.4 → 4.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,10 +28,8 @@ Proxy `/api` requests to `http://www.example.org`
28
28
 
29
29
  ```typescript
30
30
  // typescript
31
-
32
31
  import * as express from 'express';
33
- import type { Request, Response, NextFunction } from 'express';
34
-
32
+ import type { NextFunction, Request, Response } from 'express';
35
33
  import { createProxyMiddleware } from 'http-proxy-middleware';
36
34
  import type { Filter, Options, RequestHandler } from 'http-proxy-middleware';
37
35
 
@@ -163,18 +161,15 @@ http-proxy-middleware options:
163
161
  Narrow down which requests should be proxied. The `path` used for filtering is the `request.url` pathname. In Express, this is the `path` relative to the mount-point of the proxy.
164
162
 
165
163
  - **path matching**
166
-
167
164
  - `createProxyMiddleware({...})` - matches any path, all requests will be proxied when `pathFilter` is not configured.
168
165
  - `createProxyMiddleware({ pathFilter: '/api', ...})` - matches paths starting with `/api`
169
166
 
170
167
  - **multiple path matching**
171
-
172
168
  - `createProxyMiddleware({ pathFilter: ['/api', '/ajax', '/someotherpath'], ...})`
173
169
 
174
170
  - **wildcard path matching**
175
171
 
176
172
  For fine-grained control you can use wildcard matching. Glob pattern matching is done by _micromatch_. Visit [micromatch](https://www.npmjs.com/package/micromatch) or [glob](https://www.npmjs.com/package/glob) for more globbing examples.
177
-
178
173
  - `createProxyMiddleware({ pathFilter: '**', ...})` matches any path, all requests will be proxied.
179
174
  - `createProxyMiddleware({ pathFilter: '**/*.html', ...})` matches any path which ends with `.html`
180
175
  - `createProxyMiddleware({ pathFilter: '/*.html', ...})` matches paths directly under path-absolute
@@ -418,13 +413,12 @@ The following options are provided by the underlying [http-proxy](https://github
418
413
  - **option.autoRewrite**: rewrites the location host/port on (301/302/307/308) redirects based on requested host/port. Default: false.
419
414
  - **option.protocolRewrite**: rewrites the location protocol on (301/302/307/308) redirects to 'http' or 'https'. Default: null.
420
415
  - **option.cookieDomainRewrite**: rewrites domain of `set-cookie` headers. Possible values:
421
-
422
416
  - `false` (default): disable cookie rewriting
423
417
  - String: new domain, for example `cookieDomainRewrite: "new.domain"`. To remove the domain, use `cookieDomainRewrite: ""`.
424
418
  - Object: mapping of domains to new domains, use `"*"` to match all domains.
425
419
  For example keep one domain unchanged, rewrite one domain and remove other domains:
426
420
 
427
- ```json
421
+ ```jsonc
428
422
  cookieDomainRewrite: {
429
423
  "unchanged.domain": "unchanged.domain",
430
424
  "old.domain": "new.domain",
@@ -433,13 +427,12 @@ The following options are provided by the underlying [http-proxy](https://github
433
427
  ```
434
428
 
435
429
  - **option.cookiePathRewrite**: rewrites path of `set-cookie` headers. Possible values:
436
-
437
430
  - `false` (default): disable cookie rewriting
438
431
  - String: new path, for example `cookiePathRewrite: "/newPath/"`. To remove the path, use `cookiePathRewrite: ""`. To set path to root use `cookiePathRewrite: "/"`.
439
432
  - Object: mapping of paths to new paths, use `"*"` to match all paths.
440
433
  For example, to keep one path unchanged, rewrite one path and remove other paths:
441
434
 
442
- ```json
435
+ ```jsonc
443
436
  cookiePathRewrite: {
444
437
  "/unchanged.path/": "/unchanged.path/",
445
438
  "/old.path/": "/new.path/",
package/dist/factory.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import type { Options, RequestHandler, NextFunction } from './types';
2
- import type * as http from 'http';
1
+ import type * as http from 'node:http';
2
+ import type { NextFunction, Options, RequestHandler } from './types';
3
3
  export declare function createProxyMiddleware<TReq = http.IncomingMessage, TRes = http.ServerResponse, TNext = NextFunction>(options: Options<TReq, TRes>): RequestHandler<TReq, TRes, TNext>;
@@ -1,9 +1,8 @@
1
- import type * as http from 'http';
2
- import type { Options } from '../types';
1
+ import type * as http from 'node:http';
3
2
  export type BodyParserLikeRequest = http.IncomingMessage & {
4
3
  body?: any;
5
4
  };
6
5
  /**
7
6
  * Fix proxied body if bodyParser is involved.
8
7
  */
9
- export declare function fixRequestBody<TReq extends BodyParserLikeRequest = BodyParserLikeRequest>(proxyReq: http.ClientRequest, req: TReq, res: http.ServerResponse<http.IncomingMessage>, options: Options): void;
8
+ export declare function fixRequestBody<TReq extends BodyParserLikeRequest = BodyParserLikeRequest>(proxyReq: http.ClientRequest, req: TReq): void;
@@ -1,12 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fixRequestBody = fixRequestBody;
4
- const querystring = require("querystring");
5
- const logger_1 = require("../logger");
4
+ const querystring = require("node:querystring");
6
5
  /**
7
6
  * Fix proxied body if bodyParser is involved.
8
7
  */
9
- function fixRequestBody(proxyReq, req, res, options) {
8
+ function fixRequestBody(proxyReq, req) {
9
+ // skip fixRequestBody() when req.readableLength not 0 (bodyParser failure)
10
+ if (req.readableLength !== 0) {
11
+ return;
12
+ }
10
13
  const requestBody = req.body;
11
14
  if (!requestBody) {
12
15
  return;
@@ -15,19 +18,6 @@ function fixRequestBody(proxyReq, req, res, options) {
15
18
  if (!contentType) {
16
19
  return;
17
20
  }
18
- const logger = (0, logger_1.getLogger)(options);
19
- // Handle bad request when unexpected "Connect: Upgrade" header is provided
20
- if (/upgrade/gi.test(proxyReq.getHeader('Connection'))) {
21
- handleBadRequest({ proxyReq, req, res });
22
- logger.error(`[HPM] HPM_UNEXPECTED_CONNECTION_UPGRADE_HEADER. Aborted request: ${req.url}`);
23
- return;
24
- }
25
- // Handle bad request when invalid request body is provided
26
- if (hasInvalidKeys(requestBody)) {
27
- handleBadRequest({ proxyReq, req, res });
28
- logger.error(`[HPM] HPM_INVALID_REQUEST_DATA. Aborted request: ${req.url}`);
29
- return;
30
- }
31
21
  const writeBody = (bodyData) => {
32
22
  proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
33
23
  proxyReq.write(bodyData);
@@ -43,6 +33,9 @@ function fixRequestBody(proxyReq, req, res, options) {
43
33
  else if (contentType.includes('multipart/form-data')) {
44
34
  writeBody(handlerFormDataBodyData(contentType, requestBody));
45
35
  }
36
+ else if (contentType.includes('text/plain')) {
37
+ writeBody(requestBody);
38
+ }
46
39
  }
47
40
  /**
48
41
  * format FormData data
@@ -58,12 +51,3 @@ function handlerFormDataBodyData(contentType, data) {
58
51
  }
59
52
  return str;
60
53
  }
61
- function hasInvalidKeys(obj) {
62
- return Object.keys(obj).some((key) => /[\n\r]/.test(key));
63
- }
64
- function handleBadRequest({ proxyReq, req, res }) {
65
- res.writeHead(400);
66
- res.end('Bad Request');
67
- proxyReq.destroy();
68
- req.destroy();
69
- }
@@ -1,5 +1,5 @@
1
- import type * as http from 'http';
2
- type Interceptor<TReq = http.IncomingMessage, TRes = http.ServerResponse> = (buffer: Buffer, proxyRes: TReq, req: TReq, res: TRes) => Promise<Buffer | string>;
1
+ import type * as http from 'node:http';
2
+ type Interceptor<TReq = http.IncomingMessage, TRes = http.ServerResponse> = (buffer: Buffer, proxyRes: http.IncomingMessage, req: TReq, res: TRes) => Promise<Buffer | string>;
3
3
  /**
4
4
  * Intercept responses from upstream.
5
5
  * Automatically decompress (deflate, gzip, brotli).
@@ -7,5 +7,5 @@ type Interceptor<TReq = http.IncomingMessage, TRes = http.ServerResponse> = (buf
7
7
  *
8
8
  * NOTE: must set options.selfHandleResponse=true (prevent automatic call of res.end())
9
9
  */
10
- export declare function responseInterceptor<TReq extends http.IncomingMessage = http.IncomingMessage, TRes extends http.ServerResponse = http.ServerResponse>(interceptor: Interceptor<TReq, TRes>): (proxyRes: TReq, req: TReq, res: TRes) => Promise<void>;
10
+ export declare function responseInterceptor<TReq extends http.IncomingMessage = http.IncomingMessage, TRes extends http.ServerResponse = http.ServerResponse>(interceptor: Interceptor<TReq, TRes>): (proxyRes: http.IncomingMessage, req: TReq, res: TRes) => Promise<void>;
11
11
  export {};
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.responseInterceptor = responseInterceptor;
4
- const zlib = require("zlib");
4
+ const zlib = require("node:zlib");
5
5
  const debug_1 = require("../debug");
6
6
  const function_1 = require("../utils/function");
7
7
  const debug = debug_1.Debug.extend('response-interceptor');
@@ -1,4 +1,4 @@
1
- import type { RequestHandler, Options } from './types';
1
+ import type { Options, RequestHandler } from './types';
2
2
  export declare class HttpProxyMiddleware<TReq, TRes> {
3
3
  private wsInternalSubscribed;
4
4
  private serverOnCloseSubscribed;
@@ -1,145 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HttpProxyMiddleware = void 0;
4
- const httpProxy = require("http-proxy");
4
+ const httpxy_1 = require("httpxy");
5
5
  const configuration_1 = require("./configuration");
6
+ const debug_1 = require("./debug");
6
7
  const get_plugins_1 = require("./get-plugins");
8
+ const logger_1 = require("./logger");
7
9
  const path_filter_1 = require("./path-filter");
8
10
  const PathRewriter = require("./path-rewriter");
9
11
  const Router = require("./router");
10
- const debug_1 = require("./debug");
11
12
  const function_1 = require("./utils/function");
12
- const logger_1 = require("./logger");
13
13
  class HttpProxyMiddleware {
14
+ wsInternalSubscribed = false;
15
+ serverOnCloseSubscribed = false;
16
+ proxyOptions;
17
+ proxy;
18
+ pathRewriter;
19
+ logger;
14
20
  constructor(options) {
15
- this.wsInternalSubscribed = false;
16
- this.serverOnCloseSubscribed = false;
17
- // https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript#red-flags-for-this
18
- this.middleware = (async (req, res, next) => {
19
- if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
20
- try {
21
- const activeProxyOptions = await this.prepareProxyRequest(req);
22
- (0, debug_1.Debug)(`proxy request to target: %O`, activeProxyOptions.target);
23
- this.proxy.web(req, res, activeProxyOptions);
24
- }
25
- catch (err) {
26
- next?.(err);
27
- }
28
- }
29
- else {
30
- next?.();
31
- }
32
- /**
33
- * Get the server object to subscribe to server events;
34
- * 'upgrade' for websocket and 'close' for graceful shutdown
35
- *
36
- * NOTE:
37
- * req.socket: node >= 13
38
- * req.connection: node < 13 (Remove this when node 12/13 support is dropped)
39
- */
40
- const server = (req.socket ?? req.connection)?.server;
41
- if (server && !this.serverOnCloseSubscribed) {
42
- server.on('close', () => {
43
- (0, debug_1.Debug)('server close signal received: closing proxy server');
44
- this.proxy.close();
45
- });
46
- this.serverOnCloseSubscribed = true;
47
- }
48
- if (this.proxyOptions.ws === true) {
49
- // use initial request to access the server object to subscribe to http upgrade event
50
- this.catchUpgradeRequest(server);
51
- }
52
- });
53
- this.catchUpgradeRequest = (server) => {
54
- if (!this.wsInternalSubscribed) {
55
- (0, debug_1.Debug)('subscribing to server upgrade event');
56
- server.on('upgrade', this.handleUpgrade);
57
- // prevent duplicate upgrade handling;
58
- // in case external upgrade is also configured
59
- this.wsInternalSubscribed = true;
60
- }
61
- };
62
- this.handleUpgrade = async (req, socket, head) => {
63
- try {
64
- if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
65
- const activeProxyOptions = await this.prepareProxyRequest(req);
66
- this.proxy.ws(req, socket, head, activeProxyOptions);
67
- (0, debug_1.Debug)('server upgrade event received. Proxying WebSocket');
68
- }
69
- }
70
- catch (err) {
71
- // This error does not include the URL as the fourth argument as we won't
72
- // have the URL if `this.prepareProxyRequest` throws an error.
73
- this.proxy.emit('error', err, req, socket);
74
- }
75
- };
76
- /**
77
- * Determine whether request should be proxied.
78
- */
79
- this.shouldProxy = (pathFilter, req) => {
80
- try {
81
- return (0, path_filter_1.matchPathFilter)(pathFilter, req.url, req);
82
- }
83
- catch (err) {
84
- (0, debug_1.Debug)('Error: matchPathFilter() called with request url: ', `"${req.url}"`);
85
- this.logger.error(err);
86
- return false;
87
- }
88
- };
89
- /**
90
- * Apply option.router and option.pathRewrite
91
- * Order matters:
92
- * Router uses original path for routing;
93
- * NOT the modified path, after it has been rewritten by pathRewrite
94
- * @param {Object} req
95
- * @return {Object} proxy options
96
- */
97
- this.prepareProxyRequest = async (req) => {
98
- /**
99
- * Incorrect usage confirmed: https://github.com/expressjs/express/issues/4854#issuecomment-1066171160
100
- * Temporary restore req.url patch for {@link src/legacy/create-proxy-middleware.ts legacyCreateProxyMiddleware()}
101
- * FIXME: remove this patch in future release
102
- */
103
- if (this.middleware.__LEGACY_HTTP_PROXY_MIDDLEWARE__) {
104
- req.url = req.originalUrl || req.url;
105
- }
106
- const newProxyOptions = Object.assign({}, this.proxyOptions);
107
- // Apply in order:
108
- // 1. option.router
109
- // 2. option.pathRewrite
110
- await this.applyRouter(req, newProxyOptions);
111
- await this.applyPathRewrite(req, this.pathRewriter);
112
- return newProxyOptions;
113
- };
114
- // Modify option.target when router present.
115
- this.applyRouter = async (req, options) => {
116
- let newTarget;
117
- if (options.router) {
118
- newTarget = await Router.getTarget(req, options);
119
- if (newTarget) {
120
- (0, debug_1.Debug)('router new target: "%s"', newTarget);
121
- options.target = newTarget;
122
- }
123
- }
124
- };
125
- // rewrite path
126
- this.applyPathRewrite = async (req, pathRewriter) => {
127
- if (pathRewriter) {
128
- const path = await pathRewriter(req.url, req);
129
- if (typeof path === 'string') {
130
- (0, debug_1.Debug)('pathRewrite new path: %s', req.url);
131
- req.url = path;
132
- }
133
- else {
134
- (0, debug_1.Debug)('pathRewrite: no rewritten path found: %s', req.url);
135
- }
136
- }
137
- };
138
21
  (0, configuration_1.verifyConfig)(options);
139
22
  this.proxyOptions = options;
140
23
  this.logger = (0, logger_1.getLogger)(options);
141
24
  (0, debug_1.Debug)(`create proxy server`);
142
- this.proxy = httpProxy.createProxyServer({});
25
+ this.proxy = (0, httpxy_1.createProxyServer)({});
143
26
  this.registerPlugins(this.proxy, this.proxyOptions);
144
27
  this.pathRewriter = PathRewriter.createPathRewriter(this.proxyOptions.pathRewrite); // returns undefined when "pathRewrite" is not provided
145
28
  // https://github.com/chimurai/http-proxy-middleware/issues/19
@@ -150,6 +33,67 @@ class HttpProxyMiddleware {
150
33
  }
151
34
  };
152
35
  }
36
+ // https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript#red-flags-for-this
37
+ middleware = (async (req, res, next) => {
38
+ if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
39
+ let activeProxyOptions;
40
+ try {
41
+ // Preparation Phase: Apply router and path rewriter.
42
+ activeProxyOptions = await this.prepareProxyRequest(req);
43
+ // [Smoking Gun] httpxy is inconsistent with error handling:
44
+ // 1. If target is missing (here), it emits 'error' but returns a boolean (bypassing our catch/next).
45
+ // 2. If a network error occurs (in proxy.web), it rejects the promise but SKIPS emitting 'error'.
46
+ // We manually throw here to force Case 1 into the catch block so next(err) is called for Express.
47
+ if (!activeProxyOptions.target && !activeProxyOptions.forward) {
48
+ throw new Error('Must provide a proper URL as target');
49
+ }
50
+ }
51
+ catch (err) {
52
+ next?.(err);
53
+ return;
54
+ }
55
+ try {
56
+ // Proxying Phase: Handle the actual web request.
57
+ (0, debug_1.Debug)(`proxy request to target: %O`, activeProxyOptions.target);
58
+ await this.proxy.web(req, res, activeProxyOptions);
59
+ }
60
+ catch (err) {
61
+ // Manually emit 'error' event because httpxy's promise-based API does not emit it automatically.
62
+ // This is crucial for backward compatibility with HPM plugins (like error-response-plugin)
63
+ // and custom listeners registered via the 'on: { error: ... }' option.
64
+ /**
65
+ * TODO: Ideally, TReq and TRes should be restricted via "TReq extends http.IncomingMessage = http.IncomingMessage"
66
+ * and "TRes extends http.ServerResponse = http.ServerResponse", which allows us to avoid the "req as TReq" below.
67
+ *
68
+ * However, making TReq and TRes constrained types may cause a breaking change for TypeScript users downstream.
69
+ * So we leave this as a TODO for now, and revisit it in a future major release.
70
+ */
71
+ this.proxy.emit('error', err, req, res, activeProxyOptions.target);
72
+ next?.(err);
73
+ }
74
+ }
75
+ else {
76
+ next?.();
77
+ }
78
+ /**
79
+ * Get the server object to subscribe to server events;
80
+ * 'upgrade' for websocket and 'close' for graceful shutdown
81
+ */
82
+ const server = req.socket?.server;
83
+ if (server && !this.serverOnCloseSubscribed) {
84
+ server.on('close', () => {
85
+ (0, debug_1.Debug)('server close signal received: closing proxy server');
86
+ this.proxy.close(() => {
87
+ (0, debug_1.Debug)('proxy server closed');
88
+ });
89
+ });
90
+ this.serverOnCloseSubscribed = true;
91
+ }
92
+ if (this.proxyOptions.ws === true) {
93
+ // use initial request to access the server object to subscribe to http upgrade event
94
+ this.catchUpgradeRequest(server);
95
+ }
96
+ });
153
97
  registerPlugins(proxy, options) {
154
98
  const plugins = (0, get_plugins_1.getPlugins)(options);
155
99
  plugins.forEach((plugin) => {
@@ -157,5 +101,97 @@ class HttpProxyMiddleware {
157
101
  plugin(proxy, options);
158
102
  });
159
103
  }
104
+ catchUpgradeRequest = (server) => {
105
+ if (!this.wsInternalSubscribed) {
106
+ (0, debug_1.Debug)('subscribing to server upgrade event');
107
+ server.on('upgrade', this.handleUpgrade);
108
+ // prevent duplicate upgrade handling;
109
+ // in case external upgrade is also configured
110
+ this.wsInternalSubscribed = true;
111
+ }
112
+ };
113
+ handleUpgrade = async (req, socket, head) => {
114
+ try {
115
+ if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
116
+ const activeProxyOptions = await this.prepareProxyRequest(req);
117
+ await this.proxy.ws(req, socket, activeProxyOptions, head);
118
+ (0, debug_1.Debug)('server upgrade event received. Proxying WebSocket');
119
+ }
120
+ }
121
+ catch (err) {
122
+ // This error does not include the URL as the fourth argument as we won't
123
+ // have the URL if `this.prepareProxyRequest` throws an error.
124
+ /**
125
+ * TODO: Ideally, TReq and TRes should be restricted via "TReq extends http.IncomingMessage = http.IncomingMessage"
126
+ * and "TRes extends http.ServerResponse = http.ServerResponse", which allows us to avoid the "req as TReq" below.
127
+ *
128
+ * However, making TReq and TRes constrained types may cause a breaking change for TypeScript users downstream.
129
+ * So we leave this as a TODO for now, and revisit it in a future major release.
130
+ */
131
+ this.proxy.emit('error', err, req, socket);
132
+ }
133
+ };
134
+ /**
135
+ * Determine whether request should be proxied.
136
+ */
137
+ shouldProxy = (pathFilter, req) => {
138
+ try {
139
+ return (0, path_filter_1.matchPathFilter)(pathFilter, req.url, req);
140
+ }
141
+ catch (err) {
142
+ (0, debug_1.Debug)('Error: matchPathFilter() called with request url: ', `"${req.url}"`);
143
+ this.logger.error(err);
144
+ return false;
145
+ }
146
+ };
147
+ /**
148
+ * Apply option.router and option.pathRewrite
149
+ * Order matters:
150
+ * Router uses original path for routing;
151
+ * NOT the modified path, after it has been rewritten by pathRewrite
152
+ * @param {Object} req
153
+ * @return {Object} proxy options
154
+ */
155
+ prepareProxyRequest = async (req) => {
156
+ /**
157
+ * Incorrect usage confirmed: https://github.com/expressjs/express/issues/4854#issuecomment-1066171160
158
+ * Temporary restore req.url patch for {@link src/legacy/create-proxy-middleware.ts legacyCreateProxyMiddleware()}
159
+ * FIXME: remove this patch in future release
160
+ */
161
+ if (this.middleware.__LEGACY_HTTP_PROXY_MIDDLEWARE__) {
162
+ req.url = req.originalUrl || req.url;
163
+ }
164
+ const newProxyOptions = Object.assign({}, this.proxyOptions);
165
+ // Apply in order:
166
+ // 1. option.router
167
+ // 2. option.pathRewrite
168
+ await this.applyRouter(req, newProxyOptions);
169
+ await this.applyPathRewrite(req, this.pathRewriter);
170
+ return newProxyOptions;
171
+ };
172
+ // Modify option.target when router present.
173
+ applyRouter = async (req, options) => {
174
+ let newTarget;
175
+ if (options.router) {
176
+ newTarget = await Router.getTarget(req, options);
177
+ if (newTarget) {
178
+ (0, debug_1.Debug)('router new target: "%s"', newTarget);
179
+ options.target = newTarget;
180
+ }
181
+ }
182
+ };
183
+ // rewrite path
184
+ applyPathRewrite = async (req, pathRewriter) => {
185
+ if (pathRewriter) {
186
+ const path = await pathRewriter(req.url, req);
187
+ if (typeof path === 'string') {
188
+ (0, debug_1.Debug)('pathRewrite new path: %s', req.url);
189
+ req.url = path;
190
+ }
191
+ else {
192
+ (0, debug_1.Debug)('pathRewrite: no rewritten path found: %s', req.url);
193
+ }
194
+ }
195
+ };
160
196
  }
161
197
  exports.HttpProxyMiddleware = HttpProxyMiddleware;
@@ -1,6 +1,6 @@
1
+ import type * as http from 'node:http';
1
2
  import { Filter, RequestHandler } from '../types';
2
3
  import { LegacyOptions } from './types';
3
- import type * as http from 'http';
4
4
  /**
5
5
  * @deprecated
6
6
  * This function is deprecated and will be removed in a future version.
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.legacyCreateProxyMiddleware = legacyCreateProxyMiddleware;
4
- const factory_1 = require("../factory");
5
4
  const debug_1 = require("../debug");
5
+ const factory_1 = require("../factory");
6
6
  const options_adapter_1 = require("./options-adapter");
7
7
  const debug = debug_1.Debug.extend('legacy-create-proxy-middleware');
8
8
  function legacyCreateProxyMiddleware(legacyContext, legacyOptions) {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.legacyOptionsAdapter = legacyOptionsAdapter;
4
- const url = require("url");
4
+ const url = require("node:url");
5
5
  const debug_1 = require("../debug");
6
6
  const logger_1 = require("../logger");
7
7
  const debug = debug_1.Debug.extend('legacy-options-adapter');
@@ -1,4 +1,4 @@
1
- import type * as http from 'http';
1
+ import type * as http from 'node:http';
2
2
  import { Options } from '../types';
3
3
  /**
4
4
  * @deprecated
@@ -1,3 +1,3 @@
1
+ import type * as http from 'node:http';
1
2
  import type { Filter } from './types';
2
- import type * as http from 'http';
3
3
  export declare function matchPathFilter<TReq = http.IncomingMessage>(pathFilter: Filter<TReq> | undefined, uri: string | undefined, req: http.IncomingMessage): boolean;
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.matchPathFilter = matchPathFilter;
4
+ const url = require("node:url");
4
5
  const isGlob = require("is-glob");
5
6
  const micromatch = require("micromatch");
6
- const url = require("url");
7
7
  const errors_1 = require("./errors");
8
8
  function matchPathFilter(pathFilter = '/', uri, req) {
9
9
  // single path
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createPathRewriter = createPathRewriter;
4
4
  const is_plain_object_1 = require("is-plain-object");
5
- const errors_1 = require("./errors");
6
5
  const debug_1 = require("./debug");
6
+ const errors_1 = require("./errors");
7
7
  const debug = debug_1.Debug.extend('path-rewriter');
8
8
  /**
9
9
  * Create rewrite function, to cache parsed rewrite rules.
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.errorResponsePlugin = void 0;
4
4
  const status_code_1 = require("../../status-code");
5
+ const sanitize_1 = require("../../utils/sanitize");
5
6
  function isResponseLike(obj) {
6
7
  return obj && typeof obj.writeHead === 'function';
7
8
  }
@@ -11,7 +12,7 @@ function isSocketLike(obj) {
11
12
  const errorResponsePlugin = (proxyServer, options) => {
12
13
  proxyServer.on('error', (err, req, res, target) => {
13
14
  // Re-throw error. Not recoverable since req & res are empty.
14
- if (!req && !res) {
15
+ if (!req || !res) {
15
16
  throw err; // "Error: Must provide a proper URL as target"
16
17
  }
17
18
  if (isResponseLike(res)) {
@@ -20,7 +21,7 @@ const errorResponsePlugin = (proxyServer, options) => {
20
21
  res.writeHead(statusCode);
21
22
  }
22
23
  const host = req.headers && req.headers.host;
23
- res.end(`Error occurred while trying to proxy: ${host}${req.url}`);
24
+ res.end(`Error occurred while trying to proxy: ${(0, sanitize_1.sanitize)(host)}${(0, sanitize_1.sanitize)(req.url)}`);
24
25
  }
25
26
  else if (isSocketLike(res)) {
26
27
  res.destroy();
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loggerPlugin = void 0;
4
- const url_1 = require("url");
4
+ const node_url_1 = require("node:url");
5
5
  const logger_1 = require("../../logger");
6
6
  const logger_plugin_1 = require("../../utils/logger-plugin");
7
7
  const loggerPlugin = (proxyServer, options) => {
@@ -34,7 +34,7 @@ const loggerPlugin = (proxyServer, options) => {
34
34
  host: proxyRes.req.host,
35
35
  pathname: proxyRes.req.path,
36
36
  };
37
- target = new url_1.URL(`${obj.protocol}//${obj.host}${obj.pathname}`);
37
+ target = new node_url_1.URL(`${obj.protocol}//${obj.host}${obj.pathname}`);
38
38
  if (port) {
39
39
  target.port = port;
40
40
  }
@@ -43,7 +43,7 @@ const loggerPlugin = (proxyServer, options) => {
43
43
  catch (err) {
44
44
  // nock issue (https://github.com/chimurai/http-proxy-middleware/issues/1035)
45
45
  // fallback to old implementation (less correct - without port)
46
- target = new url_1.URL(options.target);
46
+ target = new node_url_1.URL(options.target);
47
47
  target.pathname = proxyRes.req.path;
48
48
  }
49
49
  const targetUrl = target.toString();
@@ -25,9 +25,21 @@ const debug = debug_1.Debug.extend('proxy-events-plugin');
25
25
  * ```
26
26
  */
27
27
  const proxyEventsPlugin = (proxyServer, options) => {
28
- Object.entries(options.on || {}).forEach(([eventName, handler]) => {
29
- debug(`register event handler: "${eventName}" -> "${(0, function_1.getFunctionName)(handler)}"`);
30
- proxyServer.on(eventName, handler);
31
- });
28
+ if (!options.on) {
29
+ return;
30
+ }
31
+ // hoist variable here for better typing
32
+ let eventName;
33
+ // for in provide better typing than Object.entries()
34
+ for (eventName in options.on) {
35
+ if (Object.prototype.hasOwnProperty.call(options.on, eventName)) {
36
+ const handler = options.on[eventName];
37
+ if (!handler) {
38
+ continue;
39
+ }
40
+ debug(`register event handler: "${eventName}" -> "${(0, function_1.getFunctionName)(handler)}"`);
41
+ proxyServer.on(eventName, handler);
42
+ }
43
+ }
32
44
  };
33
45
  exports.proxyEventsPlugin = proxyEventsPlugin;
package/dist/types.d.ts CHANGED
@@ -2,9 +2,9 @@
2
2
  * Based on definition by DefinitelyTyped:
3
3
  * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/6f529c6c67a447190f86bfbf894d1061e41e07b7/types/http-proxy-middleware/index.d.ts
4
4
  */
5
- import type * as http from 'http';
6
- import type * as httpProxy from 'http-proxy';
7
- import type * as net from 'net';
5
+ import type * as http from 'node:http';
6
+ import type * as net from 'node:net';
7
+ import type { ProxyServer, ProxyServerOptions } from 'httpxy';
8
8
  export type NextFunction<T = (err?: any) => void> = T;
9
9
  export interface RequestHandler<TReq = http.IncomingMessage, TRes = http.ServerResponse, TNext = NextFunction> {
10
10
  (req: TReq, res: TRes, next?: TNext): Promise<void>;
@@ -12,21 +12,21 @@ export interface RequestHandler<TReq = http.IncomingMessage, TRes = http.ServerR
12
12
  }
13
13
  export type Filter<TReq = http.IncomingMessage> = string | string[] | ((pathname: string, req: TReq) => boolean);
14
14
  export interface Plugin<TReq = http.IncomingMessage, TRes = http.ServerResponse> {
15
- (proxyServer: httpProxy<TReq, TRes>, options: Options<TReq, TRes>): void;
15
+ (proxyServer: ProxyServer<TReq, TRes>, options: Options<TReq, TRes>): void;
16
16
  }
17
17
  export interface OnProxyEvent<TReq = http.IncomingMessage, TRes = http.ServerResponse> {
18
- error?: httpProxy.ErrorCallback<Error, TReq, TRes>;
19
- proxyReq?: httpProxy.ProxyReqCallback<http.ClientRequest, TReq, TRes>;
20
- proxyReqWs?: httpProxy.ProxyReqWsCallback<http.ClientRequest, TReq>;
21
- proxyRes?: httpProxy.ProxyResCallback<TReq, TRes>;
22
- open?: httpProxy.OpenCallback;
23
- close?: httpProxy.CloseCallback<TReq>;
24
- start?: httpProxy.StartCallback<TReq, TRes>;
25
- end?: httpProxy.EndCallback<TReq, TRes>;
26
- econnreset?: httpProxy.EconnresetCallback<Error, TReq, TRes>;
18
+ error?: (err: Error, req: TReq, res: TRes | net.Socket, target?: string | Partial<URL>) => void;
19
+ proxyReq?: (proxyReq: http.ClientRequest, req: TReq, res: TRes, options: ProxyServerOptions) => void;
20
+ proxyReqWs?: (proxyReq: http.ClientRequest, req: TReq, socket: net.Socket, options: ProxyServerOptions, head: any) => void;
21
+ proxyRes?: (proxyRes: TReq, req: TReq, res: TRes) => void;
22
+ open?: (proxySocket: net.Socket) => void;
23
+ close?: (proxyRes: TReq, proxySocket: net.Socket, proxyHead: any) => void;
24
+ start?: (req: TReq, res: TRes, target: string | Partial<URL>) => void;
25
+ end?: (req: TReq, res: TRes, proxyRes: TReq) => void;
26
+ econnreset?: (err: Error, req: TReq, res: TRes, target: string | Partial<URL>) => void;
27
27
  }
28
28
  export type Logger = Pick<Console, 'info' | 'warn' | 'error'>;
29
- export interface Options<TReq = http.IncomingMessage, TRes = http.ServerResponse> extends httpProxy.ServerOptions {
29
+ export interface Options<TReq = http.IncomingMessage, TRes = http.ServerResponse> extends ProxyServerOptions {
30
30
  /**
31
31
  * Narrow down requests to proxy or not.
32
32
  * Filter on {@link http.IncomingMessage.url `pathname`} which is relative to the proxy's "mounting" point in the server.
@@ -105,8 +105,8 @@ export interface Options<TReq = http.IncomingMessage, TRes = http.ServerResponse
105
105
  * @link https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/router.md
106
106
  */
107
107
  router?: {
108
- [hostOrPath: string]: httpProxy.ServerOptions['target'];
109
- } | ((req: TReq) => httpProxy.ServerOptions['target']) | ((req: TReq) => Promise<httpProxy.ServerOptions['target']>);
108
+ [hostOrPath: string]: ProxyServerOptions['target'];
109
+ } | ((req: TReq) => ProxyServerOptions['target']) | ((req: TReq) => Promise<ProxyServerOptions['target']>);
110
110
  /**
111
111
  * Log information from http-proxy-middleware
112
112
  * @example
@@ -118,5 +118,5 @@ export interface Options<TReq = http.IncomingMessage, TRes = http.ServerResponse
118
118
  * @link https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/logger.md
119
119
  * @since v3.0.0
120
120
  */
121
- logger?: Logger | any;
121
+ logger?: Logger;
122
122
  }
package/dist/types.js CHANGED
@@ -1,6 +1,2 @@
1
1
  "use strict";
2
- /**
3
- * Based on definition by DefinitelyTyped:
4
- * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/6f529c6c67a447190f86bfbf894d1061e41e07b7/types/http-proxy-middleware/index.d.ts
5
- */
6
2
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export declare function sanitize(input: string | undefined): string;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sanitize = sanitize;
4
+ function sanitize(input) {
5
+ return input?.replace(/[<>]/g, (i) => encodeURIComponent(i)) ?? '';
6
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "http-proxy-middleware",
3
3
  "type": "commonjs",
4
- "version": "3.0.4",
4
+ "version": "4.0.0-beta.0",
5
5
  "description": "The one-liner node.js proxy middleware for connect, express, next.js and more",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -56,49 +56,50 @@
56
56
  },
57
57
  "homepage": "https://github.com/chimurai/http-proxy-middleware#readme",
58
58
  "devDependencies": {
59
- "@commitlint/cli": "19.8.0",
60
- "@commitlint/config-conventional": "19.8.0",
61
- "@eslint/js": "9.23.0",
59
+ "@commitlint/cli": "20.4.2",
60
+ "@commitlint/config-conventional": "20.4.2",
61
+ "@eslint/js": "10.0.1",
62
+ "@trivago/prettier-plugin-sort-imports": "6.0.2",
62
63
  "@types/debug": "4.1.12",
63
64
  "@types/eslint": "9.6.1",
64
- "@types/express": "4.17.21",
65
+ "@types/express": "5.0.6",
65
66
  "@types/is-glob": "4.0.4",
66
- "@types/jest": "29.5.14",
67
- "@types/micromatch": "4.0.9",
68
- "@types/node": "22.10.2",
69
- "@types/supertest": "6.0.2",
70
- "@types/ws": "8.18.0",
71
- "body-parser": "1.20.3",
72
- "eslint": "9.23.0",
73
- "eslint-config-prettier": "10.1.1",
74
- "eslint-plugin-prettier": "5.2.3",
75
- "express": "4.21.2",
67
+ "@types/jest": "30.0.0",
68
+ "@types/micromatch": "4.0.10",
69
+ "@types/node": "24.10.2",
70
+ "@types/supertest": "7.2.0",
71
+ "@types/ws": "8.18.1",
72
+ "body-parser": "2.2.2",
73
+ "eslint": "10.0.2",
74
+ "express": "5.2.1",
76
75
  "get-port": "5.1.1",
77
- "globals": "16.0.0",
76
+ "globals": "17.3.0",
78
77
  "husky": "9.1.7",
79
- "jest": "29.7.0",
80
- "lint-staged": "15.5.0",
81
- "mockttp": "3.17.0",
78
+ "jest": "30.2.0",
79
+ "lint-staged": "16.3.0",
80
+ "mockttp": "4.2.1",
82
81
  "open": "8.4.2",
83
- "patch-package": "8.0.0",
84
- "pkg-pr-new": "0.0.41",
85
- "prettier": "3.5.3",
86
- "supertest": "7.1.0",
87
- "ts-jest": "29.2.6",
88
- "typescript": "5.8.2",
89
- "typescript-eslint": "8.27.0",
90
- "ws": "8.18.1"
82
+ "patch-package": "8.0.1",
83
+ "pkg-pr-new": "0.0.65",
84
+ "prettier": "3.8.1",
85
+ "supertest": "7.2.2",
86
+ "ts-jest": "29.4.6",
87
+ "typescript": "5.9.3",
88
+ "typescript-eslint": "8.56.1",
89
+ "ws": "8.19.0"
91
90
  },
92
91
  "dependencies": {
93
- "@types/http-proxy": "^1.17.15",
94
92
  "debug": "^4.3.6",
95
- "http-proxy": "^1.18.1",
93
+ "httpxy": "^0.2.2",
96
94
  "is-glob": "^4.0.3",
97
95
  "is-plain-object": "^5.0.0",
98
96
  "micromatch": "^4.0.8"
99
97
  },
98
+ "resolutions": {
99
+ "patch-package/**/tmp": "^0.2.4"
100
+ },
100
101
  "engines": {
101
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
102
+ "node": ">=20.0.0"
102
103
  },
103
104
  "commitlint": {
104
105
  "extends": [