http-proxy-middleware 3.0.2 → 3.0.4

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
@@ -38,9 +38,9 @@ import type { Filter, Options, RequestHandler } from 'http-proxy-middleware';
38
38
  const app = express();
39
39
 
40
40
  const proxyMiddleware = createProxyMiddleware<Request, Response>({
41
- target: 'http://www.example.org/api',
42
- changeOrigin: true,
43
- }),
41
+ target: 'http://www.example.org/api',
42
+ changeOrigin: true,
43
+ });
44
44
 
45
45
  app.use('/api', proxyMiddleware);
46
46
 
@@ -48,7 +48,6 @@ app.listen(3000);
48
48
 
49
49
  // proxy and keep the same base path "/api"
50
50
  // http://127.0.0.1:3000/api/foo/bar -> http://www.example.org/api/foo/bar
51
-
52
51
  ```
53
52
 
54
53
  _All_ `http-proxy` [options](https://github.com/nodejitsu/node-http-proxy#options) can be used, along with some extra `http-proxy-middleware` [options](#options).
@@ -645,4 +644,4 @@ $ yarn spellcheck
645
644
 
646
645
  The MIT License (MIT)
647
646
 
648
- Copyright (c) 2015-2024 Steven Chim
647
+ Copyright (c) 2015-2025 Steven Chim
@@ -1,8 +1,9 @@
1
1
  import type * as http from 'http';
2
+ import type { Options } from '../types';
2
3
  export type BodyParserLikeRequest = http.IncomingMessage & {
3
- body: any;
4
+ body?: any;
4
5
  };
5
6
  /**
6
7
  * Fix proxied body if bodyParser is involved.
7
8
  */
8
- export declare function fixRequestBody<TReq = http.IncomingMessage>(proxyReq: http.ClientRequest, req: TReq): void;
9
+ export declare function fixRequestBody<TReq extends BodyParserLikeRequest = BodyParserLikeRequest>(proxyReq: http.ClientRequest, req: TReq, res: http.ServerResponse<http.IncomingMessage>, options: Options): void;
@@ -2,24 +2,68 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fixRequestBody = fixRequestBody;
4
4
  const querystring = require("querystring");
5
+ const logger_1 = require("../logger");
5
6
  /**
6
7
  * Fix proxied body if bodyParser is involved.
7
8
  */
8
- function fixRequestBody(proxyReq, req) {
9
+ function fixRequestBody(proxyReq, req, res, options) {
9
10
  const requestBody = req.body;
10
11
  if (!requestBody) {
11
12
  return;
12
13
  }
13
14
  const contentType = proxyReq.getHeader('Content-Type');
15
+ if (!contentType) {
16
+ return;
17
+ }
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
+ }
14
31
  const writeBody = (bodyData) => {
15
- // deepcode ignore ContentLengthInCode: bodyParser fix
16
32
  proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
17
33
  proxyReq.write(bodyData);
18
34
  };
19
- if (contentType && (contentType.includes('application/json') || contentType.includes('+json'))) {
35
+ // Use if-elseif to prevent multiple writeBody/setHeader calls:
36
+ // Error: "Cannot set headers after they are sent to the client"
37
+ if (contentType.includes('application/json') || contentType.includes('+json')) {
20
38
  writeBody(JSON.stringify(requestBody));
21
39
  }
22
- if (contentType && contentType.includes('application/x-www-form-urlencoded')) {
40
+ else if (contentType.includes('application/x-www-form-urlencoded')) {
23
41
  writeBody(querystring.stringify(requestBody));
24
42
  }
43
+ else if (contentType.includes('multipart/form-data')) {
44
+ writeBody(handlerFormDataBodyData(contentType, requestBody));
45
+ }
46
+ }
47
+ /**
48
+ * format FormData data
49
+ * @param contentType
50
+ * @param data
51
+ * @returns
52
+ */
53
+ function handlerFormDataBodyData(contentType, data) {
54
+ const boundary = contentType.replace(/^.*boundary=(.*)$/, '$1');
55
+ let str = '';
56
+ for (const [key, value] of Object.entries(data)) {
57
+ str += `--${boundary}\r\nContent-Disposition: form-data; name="${key}"\r\n\r\n${value}\r\n`;
58
+ }
59
+ return str;
60
+ }
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();
25
69
  }
@@ -5,6 +5,7 @@ export declare class HttpProxyMiddleware<TReq, TRes> {
5
5
  private proxyOptions;
6
6
  private proxy;
7
7
  private pathRewriter;
8
+ private logger;
8
9
  constructor(options: Options<TReq, TRes>);
9
10
  middleware: RequestHandler;
10
11
  private registerPlugins;
@@ -9,6 +9,7 @@ const PathRewriter = require("./path-rewriter");
9
9
  const Router = require("./router");
10
10
  const debug_1 = require("./debug");
11
11
  const function_1 = require("./utils/function");
12
+ const logger_1 = require("./logger");
12
13
  class HttpProxyMiddleware {
13
14
  constructor(options) {
14
15
  this.wsInternalSubscribed = false;
@@ -59,17 +60,31 @@ class HttpProxyMiddleware {
59
60
  }
60
61
  };
61
62
  this.handleUpgrade = async (req, socket, head) => {
62
- if (this.shouldProxy(this.proxyOptions.pathFilter, req)) {
63
- const activeProxyOptions = await this.prepareProxyRequest(req);
64
- this.proxy.ws(req, socket, head, activeProxyOptions);
65
- (0, debug_1.Debug)('server upgrade event received. Proxying WebSocket');
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);
66
74
  }
67
75
  };
68
76
  /**
69
77
  * Determine whether request should be proxied.
70
78
  */
71
79
  this.shouldProxy = (pathFilter, req) => {
72
- return (0, path_filter_1.matchPathFilter)(pathFilter, req.url, 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
+ }
73
88
  };
74
89
  /**
75
90
  * Apply option.router and option.pathRewrite
@@ -122,6 +137,7 @@ class HttpProxyMiddleware {
122
137
  };
123
138
  (0, configuration_1.verifyConfig)(options);
124
139
  this.proxyOptions = options;
140
+ this.logger = (0, logger_1.getLogger)(options);
125
141
  (0, debug_1.Debug)(`create proxy server`);
126
142
  this.proxy = httpProxy.createProxyServer({});
127
143
  this.registerPlugins(this.proxy, this.proxyOptions);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './factory';
2
2
  export * from './handlers';
3
- export type { Filter, Options, RequestHandler } from './types';
3
+ export type { Plugin, Filter, Options, RequestHandler } from './types';
4
4
  /**
5
5
  * Default plugins
6
6
  */
@@ -2,18 +2,29 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.errorResponsePlugin = void 0;
4
4
  const status_code_1 = require("../../status-code");
5
+ function isResponseLike(obj) {
6
+ return obj && typeof obj.writeHead === 'function';
7
+ }
8
+ function isSocketLike(obj) {
9
+ return obj && typeof obj.write === 'function' && !('writeHead' in obj);
10
+ }
5
11
  const errorResponsePlugin = (proxyServer, options) => {
6
12
  proxyServer.on('error', (err, req, res, target) => {
7
13
  // Re-throw error. Not recoverable since req & res are empty.
8
14
  if (!req && !res) {
9
15
  throw err; // "Error: Must provide a proper URL as target"
10
16
  }
11
- if ('writeHead' in res && !res.headersSent) {
12
- const statusCode = (0, status_code_1.getStatusCode)(err.code);
13
- res.writeHead(statusCode);
17
+ if (isResponseLike(res)) {
18
+ if (!res.headersSent) {
19
+ const statusCode = (0, status_code_1.getStatusCode)(err.code);
20
+ res.writeHead(statusCode);
21
+ }
22
+ const host = req.headers && req.headers.host;
23
+ res.end(`Error occurred while trying to proxy: ${host}${req.url}`);
24
+ }
25
+ else if (isSocketLike(res)) {
26
+ res.destroy();
14
27
  }
15
- const host = req.headers && req.headers.host;
16
- res.end(`Error occurred while trying to proxy: ${host}${req.url}`);
17
28
  });
18
29
  };
19
30
  exports.errorResponsePlugin = errorResponsePlugin;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "http-proxy-middleware",
3
3
  "type": "commonjs",
4
- "version": "3.0.2",
4
+ "version": "3.0.4",
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",
@@ -20,7 +20,7 @@
20
20
  "build": "tsc --build",
21
21
  "test": "jest",
22
22
  "coverage": "jest --coverage",
23
- "prepare": "husky",
23
+ "prepare": "husky && patch-package",
24
24
  "prepack": "yarn clean && yarn test && yarn build",
25
25
  "spellcheck": "npx --yes cspell --show-context --show-suggestions '**/*.*'"
26
26
  },
@@ -56,37 +56,38 @@
56
56
  },
57
57
  "homepage": "https://github.com/chimurai/http-proxy-middleware#readme",
58
58
  "devDependencies": {
59
- "@commitlint/cli": "19.4.1",
60
- "@commitlint/config-conventional": "19.4.1",
61
- "@eslint/js": "9.9.1",
59
+ "@commitlint/cli": "19.8.0",
60
+ "@commitlint/config-conventional": "19.8.0",
61
+ "@eslint/js": "9.23.0",
62
62
  "@types/debug": "4.1.12",
63
63
  "@types/eslint": "9.6.1",
64
- "@types/eslint__js": "8.42.3",
65
64
  "@types/express": "4.17.21",
66
65
  "@types/is-glob": "4.0.4",
67
- "@types/jest": "29.5.12",
66
+ "@types/jest": "29.5.14",
68
67
  "@types/micromatch": "4.0.9",
69
- "@types/node": "22.5.1",
68
+ "@types/node": "22.10.2",
70
69
  "@types/supertest": "6.0.2",
71
- "@types/ws": "8.5.12",
72
- "body-parser": "1.20.2",
73
- "eslint": "9.9.1",
74
- "eslint-config-prettier": "9.1.0",
75
- "eslint-plugin-prettier": "5.2.1",
76
- "express": "4.19.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",
77
76
  "get-port": "5.1.1",
78
- "globals": "15.9.0",
79
- "husky": "9.1.5",
77
+ "globals": "16.0.0",
78
+ "husky": "9.1.7",
80
79
  "jest": "29.7.0",
81
- "lint-staged": "15.2.9",
82
- "mockttp": "3.15.2",
80
+ "lint-staged": "15.5.0",
81
+ "mockttp": "3.17.0",
83
82
  "open": "8.4.2",
84
- "prettier": "3.3.3",
85
- "supertest": "7.0.0",
86
- "ts-jest": "29.2.5",
87
- "typescript": "5.5.4",
88
- "typescript-eslint": "8.3.0",
89
- "ws": "8.18.0"
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"
90
91
  },
91
92
  "dependencies": {
92
93
  "@types/http-proxy": "^1.17.15",