@ohos-ports/http-proxy-middleware 0.0.0-stage → 4.2.0-beta.1

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.
Files changed (67) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +670 -2
  3. package/dist/configuration.d.ts +3 -0
  4. package/dist/configuration.js +6 -0
  5. package/dist/debug.d.ts +5 -0
  6. package/dist/debug.js +5 -0
  7. package/dist/errors.d.ts +4 -0
  8. package/dist/errors.js +15 -0
  9. package/dist/factory-hono.d.ts +28 -0
  10. package/dist/factory-hono.js +45 -0
  11. package/dist/factory.d.ts +79 -0
  12. package/dist/factory.js +81 -0
  13. package/dist/get-plugins.d.ts +3 -0
  14. package/dist/get-plugins.js +10 -0
  15. package/dist/handlers/fix-request-body-utils/stringify-form-data.d.ts +12 -0
  16. package/dist/handlers/fix-request-body-utils/stringify-form-data.js +46 -0
  17. package/dist/handlers/fix-request-body.d.ts +22 -0
  18. package/dist/handlers/fix-request-body.js +78 -0
  19. package/dist/handlers/index.d.ts +1 -0
  20. package/dist/handlers/index.js +1 -0
  21. package/dist/handlers/public.d.ts +2 -0
  22. package/dist/handlers/public.js +2 -0
  23. package/dist/handlers/response-interceptor.d.ts +27 -0
  24. package/dist/handlers/response-interceptor.js +145 -0
  25. package/dist/http-proxy-middleware.d.ts +31 -0
  26. package/dist/http-proxy-middleware.js +183 -0
  27. package/dist/index-hono.d.ts +10 -0
  28. package/dist/index-hono.js +10 -0
  29. package/dist/index.d.ts +4 -0
  30. package/dist/index.js +3 -0
  31. package/dist/logger.d.ts +2 -0
  32. package/dist/logger.js +21 -0
  33. package/dist/path-filter.d.ts +3 -0
  34. package/dist/path-filter.js +76 -0
  35. package/dist/path-rewriter.d.ts +6 -0
  36. package/dist/path-rewriter.js +59 -0
  37. package/dist/plugins/default/debug-proxy-errors-plugin.d.ts +6 -0
  38. package/dist/plugins/default/debug-proxy-errors-plugin.js +77 -0
  39. package/dist/plugins/default/error-response-plugin.d.ts +2 -0
  40. package/dist/plugins/default/error-response-plugin.js +28 -0
  41. package/dist/plugins/default/index.d.ts +4 -0
  42. package/dist/plugins/default/index.js +4 -0
  43. package/dist/plugins/default/logger-plugin.d.ts +2 -0
  44. package/dist/plugins/default/logger-plugin.js +57 -0
  45. package/dist/plugins/default/proxy-events.d.ts +22 -0
  46. package/dist/plugins/default/proxy-events.js +42 -0
  47. package/dist/plugins/define-plugin.d.ts +25 -0
  48. package/dist/plugins/define-plugin.js +25 -0
  49. package/dist/plugins/index.d.ts +2 -0
  50. package/dist/plugins/index.js +4 -0
  51. package/dist/router.d.ts +3 -0
  52. package/dist/router.js +60 -0
  53. package/dist/status-code.d.ts +1 -0
  54. package/dist/status-code.js +23 -0
  55. package/dist/types.d.ts +139 -0
  56. package/dist/types.js +1 -0
  57. package/dist/utils/create-url.d.ts +9 -0
  58. package/dist/utils/create-url.js +17 -0
  59. package/dist/utils/function.d.ts +1 -0
  60. package/dist/utils/function.js +4 -0
  61. package/dist/utils/ipv6.d.ts +20 -0
  62. package/dist/utils/ipv6.js +66 -0
  63. package/dist/utils/logger-plugin.d.ts +7 -0
  64. package/dist/utils/logger-plugin.js +7 -0
  65. package/dist/utils/sanitize.d.ts +1 -0
  66. package/dist/utils/sanitize.js +3 -0
  67. package/package.json +120 -4
@@ -0,0 +1,3 @@
1
+ import type * as http from 'node:http';
2
+ import type { Options } from './types.js';
3
+ export declare function verifyConfig<TReq extends http.IncomingMessage, TRes extends http.ServerResponse>(options: Options<TReq, TRes>): void;
@@ -0,0 +1,6 @@
1
+ import { HttpProxyMiddlewareError } from './errors.js';
2
+ export function verifyConfig(options) {
3
+ if (!options.target && !options.router) {
4
+ throw new HttpProxyMiddlewareError('[HPM] Missing "target" option. Example: {target: "http://www.example.org"}', 'ERR_CONFIG_FACTORY_TARGET_MISSING');
5
+ }
6
+ }
@@ -0,0 +1,5 @@
1
+ import createDebug from 'debug';
2
+ /**
3
+ * Debug instance with the given namespace: http-proxy-middleware
4
+ */
5
+ export declare const Debug: createDebug.Debugger;
package/dist/debug.js ADDED
@@ -0,0 +1,5 @@
1
+ import createDebug from 'debug';
2
+ /**
3
+ * Debug instance with the given namespace: http-proxy-middleware
4
+ */
5
+ export const Debug = createDebug('http-proxy-middleware');
@@ -0,0 +1,4 @@
1
+ export declare class HttpProxyMiddlewareError extends Error {
2
+ code: string;
3
+ constructor(message: string, code: string);
4
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,15 @@
1
+ export class HttpProxyMiddlewareError extends Error {
2
+ code;
3
+ constructor(message, code) {
4
+ super(message);
5
+ // add custom `code` property
6
+ // so this can be used in src/plugins/default/error-response-plugin.ts to determine the status code to return
7
+ this.code = code;
8
+ // set the correct name for the error class
9
+ this.name = this.constructor.name;
10
+ // maintain proper stack trace (V8 environments)
11
+ if (Error.captureStackTrace) {
12
+ Error.captureStackTrace(this, this.constructor);
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,28 @@
1
+ import type { HttpBindings } from '@hono/node-server';
2
+ import type { MiddlewareHandler } from 'hono';
3
+ import { type Options } from './index.js';
4
+ /**
5
+ * Creates a Hono middleware that proxies requests using http-proxy-middleware.
6
+ *
7
+ * @remarks
8
+ * This middleware requires Hono to be running on Node.js via `@hono/node-server`.
9
+ * It uses `c.env.incoming` and `c.env.outgoing` which are only available with `HttpBindings`.
10
+ *
11
+ * @experimental This API is experimental and may change without a major version bump.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { serve } from '@hono/node-server';
16
+ * import { Hono } from 'hono';
17
+ * import { createHonoProxyMiddleware } from 'http-proxy-middleware/hono';
18
+ *
19
+ * const app = new Hono();
20
+ * app.use('/api', createHonoProxyMiddleware({ target: 'http://example.com', changeOrigin: true }));
21
+ * serve(app);
22
+ * ```
23
+ *
24
+ * @since 4.0.0
25
+ */
26
+ export declare function createHonoProxyMiddleware(options: Options): MiddlewareHandler<{
27
+ Bindings: HttpBindings;
28
+ }>;
@@ -0,0 +1,45 @@
1
+ import { createProxyMiddleware } from './index.js';
2
+ import { getLogger } from './logger.js';
3
+ /**
4
+ * Creates a Hono middleware that proxies requests using http-proxy-middleware.
5
+ *
6
+ * @remarks
7
+ * This middleware requires Hono to be running on Node.js via `@hono/node-server`.
8
+ * It uses `c.env.incoming` and `c.env.outgoing` which are only available with `HttpBindings`.
9
+ *
10
+ * @experimental This API is experimental and may change without a major version bump.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { serve } from '@hono/node-server';
15
+ * import { Hono } from 'hono';
16
+ * import { createHonoProxyMiddleware } from 'http-proxy-middleware/hono';
17
+ *
18
+ * const app = new Hono();
19
+ * app.use('/api', createHonoProxyMiddleware({ target: 'http://example.com', changeOrigin: true }));
20
+ * serve(app);
21
+ * ```
22
+ *
23
+ * @since 4.0.0
24
+ */
25
+ export function createHonoProxyMiddleware(options) {
26
+ const proxy = createProxyMiddleware(options);
27
+ const logger = getLogger(options);
28
+ return (c, next) => {
29
+ return new Promise((resolve, reject) => {
30
+ proxy(c.env.incoming, c.env.outgoing, (err) => {
31
+ if (err) {
32
+ reject(err);
33
+ }
34
+ else {
35
+ resolve();
36
+ }
37
+ });
38
+ })
39
+ .then(() => next())
40
+ .catch((err) => {
41
+ logger.error('Proxy error:', err);
42
+ return c.text('Proxy Error', 500);
43
+ });
44
+ };
45
+ }
@@ -0,0 +1,79 @@
1
+ import type * as http from 'node:http';
2
+ import type { NextFunction, Options, RequestHandler } from './types.js';
3
+ /**
4
+ * Create proxy middleware for Express-like servers. ([list of servers with examples](https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/servers.md))
5
+ *
6
+ * @example Basic proxy to a single target.
7
+ * ```ts
8
+ * import { createProxyMiddleware } from 'http-proxy-middleware';
9
+ *
10
+ * const proxy = createProxyMiddleware({
11
+ * target: 'http://www.example.org',
12
+ * changeOrigin: true,
13
+ * });
14
+ * ```
15
+ *
16
+ * @example Proxy only matching paths and rewrite the forwarded path.
17
+ * ```ts
18
+ * import { createProxyMiddleware } from 'http-proxy-middleware';
19
+ *
20
+ * const proxy = createProxyMiddleware({
21
+ * target: 'http://localhost:3000',
22
+ * pathFilter: '/api',
23
+ * pathRewrite: {
24
+ * '^/api/': '/',
25
+ * },
26
+ * });
27
+ * ```
28
+ *
29
+ * @example Native path rewrite by mounting at a route (alternative to `pathRewrite`).
30
+ * ```ts
31
+ * import express from 'express';
32
+ * import { createProxyMiddleware } from 'http-proxy-middleware';
33
+ *
34
+ * const app = express();
35
+ * app.use(
36
+ * '/users',
37
+ * createProxyMiddleware({
38
+ * target: 'http://jsonplaceholder.typicode.com/users',
39
+ * changeOrigin: true,
40
+ * }),
41
+ * );
42
+ * ```
43
+ *
44
+ * @example Use framework-specific request/response types (Express).
45
+ * ```ts
46
+ * import type { Request, Response } from 'express';
47
+ * import { createProxyMiddleware } from 'http-proxy-middleware';
48
+ *
49
+ * const proxy = createProxyMiddleware<Request, Response>({
50
+ * target: 'http://www.example.org/api',
51
+ * changeOrigin: true,
52
+ * });
53
+ * ```
54
+ *
55
+ * @example Intercept and modify a proxied response body.
56
+ * ```ts
57
+ * import { createProxyMiddleware, responseInterceptor } from 'http-proxy-middleware';
58
+ *
59
+ * const proxy = createProxyMiddleware({
60
+ * target: 'http://www.example.org',
61
+ * selfHandleResponse: true,
62
+ * on: {
63
+ * proxyRes: responseInterceptor(async (responseBuffer) => {
64
+ * const response = responseBuffer.toString('utf8');
65
+ * return response.replace('Hello', 'Goodbye');
66
+ * }),
67
+ * },
68
+ * });
69
+ * ```
70
+ *
71
+ * @see https://github.com/chimurai/http-proxy-middleware/
72
+ * @see https://github.com/chimurai/http-proxy-middleware/#basic-usage
73
+ * @see https://github.com/chimurai/http-proxy-middleware/#intercept-and-manipulate-responses
74
+ * @see https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/servers.md
75
+ * @see https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/pathFilter.md
76
+ * @see https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/pathRewrite.md
77
+ * @see https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/response-interceptor.md
78
+ */
79
+ export declare function createProxyMiddleware<TReq extends http.IncomingMessage = http.IncomingMessage, TRes extends http.ServerResponse = http.ServerResponse, TNext = NextFunction>(options: Options<TReq, TRes>): RequestHandler<TReq, TRes, TNext>;
@@ -0,0 +1,81 @@
1
+ import { HttpProxyMiddleware } from './http-proxy-middleware.js';
2
+ /**
3
+ * Create proxy middleware for Express-like servers. ([list of servers with examples](https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/servers.md))
4
+ *
5
+ * @example Basic proxy to a single target.
6
+ * ```ts
7
+ * import { createProxyMiddleware } from 'http-proxy-middleware';
8
+ *
9
+ * const proxy = createProxyMiddleware({
10
+ * target: 'http://www.example.org',
11
+ * changeOrigin: true,
12
+ * });
13
+ * ```
14
+ *
15
+ * @example Proxy only matching paths and rewrite the forwarded path.
16
+ * ```ts
17
+ * import { createProxyMiddleware } from 'http-proxy-middleware';
18
+ *
19
+ * const proxy = createProxyMiddleware({
20
+ * target: 'http://localhost:3000',
21
+ * pathFilter: '/api',
22
+ * pathRewrite: {
23
+ * '^/api/': '/',
24
+ * },
25
+ * });
26
+ * ```
27
+ *
28
+ * @example Native path rewrite by mounting at a route (alternative to `pathRewrite`).
29
+ * ```ts
30
+ * import express from 'express';
31
+ * import { createProxyMiddleware } from 'http-proxy-middleware';
32
+ *
33
+ * const app = express();
34
+ * app.use(
35
+ * '/users',
36
+ * createProxyMiddleware({
37
+ * target: 'http://jsonplaceholder.typicode.com/users',
38
+ * changeOrigin: true,
39
+ * }),
40
+ * );
41
+ * ```
42
+ *
43
+ * @example Use framework-specific request/response types (Express).
44
+ * ```ts
45
+ * import type { Request, Response } from 'express';
46
+ * import { createProxyMiddleware } from 'http-proxy-middleware';
47
+ *
48
+ * const proxy = createProxyMiddleware<Request, Response>({
49
+ * target: 'http://www.example.org/api',
50
+ * changeOrigin: true,
51
+ * });
52
+ * ```
53
+ *
54
+ * @example Intercept and modify a proxied response body.
55
+ * ```ts
56
+ * import { createProxyMiddleware, responseInterceptor } from 'http-proxy-middleware';
57
+ *
58
+ * const proxy = createProxyMiddleware({
59
+ * target: 'http://www.example.org',
60
+ * selfHandleResponse: true,
61
+ * on: {
62
+ * proxyRes: responseInterceptor(async (responseBuffer) => {
63
+ * const response = responseBuffer.toString('utf8');
64
+ * return response.replace('Hello', 'Goodbye');
65
+ * }),
66
+ * },
67
+ * });
68
+ * ```
69
+ *
70
+ * @see https://github.com/chimurai/http-proxy-middleware/
71
+ * @see https://github.com/chimurai/http-proxy-middleware/#basic-usage
72
+ * @see https://github.com/chimurai/http-proxy-middleware/#intercept-and-manipulate-responses
73
+ * @see https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/servers.md
74
+ * @see https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/pathFilter.md
75
+ * @see https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/pathRewrite.md
76
+ * @see https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/response-interceptor.md
77
+ */
78
+ export function createProxyMiddleware(options) {
79
+ const { middleware } = new HttpProxyMiddleware(options);
80
+ return middleware;
81
+ }
@@ -0,0 +1,3 @@
1
+ import type * as http from 'node:http';
2
+ import type { Options, Plugin } from './types.js';
3
+ export declare function getPlugins<TReq extends http.IncomingMessage, TRes extends http.ServerResponse>(options: Options<TReq, TRes>): Plugin<TReq, TRes>[];
@@ -0,0 +1,10 @@
1
+ import { debugProxyErrorsPlugin, errorResponsePlugin, loggerPlugin, proxyEventsPlugin, } from './plugins/default/index.js';
2
+ export function getPlugins(options) {
3
+ // don't load default errorResponsePlugin if user has specified their own
4
+ const maybeErrorResponsePlugin = options.on?.error ? [] : [errorResponsePlugin];
5
+ const defaultPlugins = options.ejectPlugins
6
+ ? [] // no default plugins when ejecting
7
+ : [debugProxyErrorsPlugin, proxyEventsPlugin, loggerPlugin, ...maybeErrorResponsePlugin];
8
+ const userPlugins = options.plugins ?? [];
9
+ return [...defaultPlugins, ...userPlugins];
10
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * HPM_ERR_INVALID_MULTIPART prefixed error code will be used in
3
+ * [status-code.ts]({@link ../../status-code.ts}) to return status code 400.
4
+ */
5
+ export declare const HPM_ERR_INVALID_MULTIPART = "HPM_ERR_INVALID_MULTIPART";
6
+ /**
7
+ * stringify FormData data
8
+ * @param contentType
9
+ * @param data
10
+ * @returns
11
+ */
12
+ export declare function stringifyFormData(contentType: string, data: object): string;
@@ -0,0 +1,46 @@
1
+ import { HttpProxyMiddlewareError } from '../../errors.js';
2
+ const CR_OR_LF = /[\r\n]/;
3
+ /**
4
+ * HPM_ERR_INVALID_MULTIPART prefixed error code will be used in
5
+ * [status-code.ts]({@link ../../status-code.ts}) to return status code 400.
6
+ */
7
+ export const HPM_ERR_INVALID_MULTIPART = 'HPM_ERR_INVALID_MULTIPART';
8
+ /**
9
+ * stringify FormData data
10
+ * @param contentType
11
+ * @param data
12
+ * @returns
13
+ */
14
+ export function stringifyFormData(contentType, data) {
15
+ const boundary = getMultipartBoundary(contentType);
16
+ let str = '';
17
+ for (const [key, value] of Object.entries(data)) {
18
+ const normalizedKey = String(key);
19
+ const normalizedValue = String(value);
20
+ // Reject potentially dangerous sequences to prevent multipart header/body injection.
21
+ validateMultipartField(normalizedKey, normalizedValue, boundary);
22
+ str += `--${boundary}\r\nContent-Disposition: form-data; name="${escapeMultipartFieldName(normalizedKey)}"\r\n\r\n${normalizedValue}\r\n`;
23
+ }
24
+ return str;
25
+ }
26
+ function getMultipartBoundary(contentType) {
27
+ const boundaryMatch = /(?:^|;)\s*boundary=(?:"([^"]+)"|([^;]+))/i.exec(contentType);
28
+ // Keep backward-compatible behavior when boundary is omitted: fall back to legacy extraction.
29
+ const boundary = (boundaryMatch?.[1] ?? boundaryMatch?.[2] ?? contentType).trim();
30
+ if (!boundary || CR_OR_LF.test(boundary)) {
31
+ throw new HttpProxyMiddlewareError('[HPM] invalid multipart boundary detected.', `${HPM_ERR_INVALID_MULTIPART}_BOUNDARY`);
32
+ }
33
+ return boundary;
34
+ }
35
+ function validateMultipartField(fieldName, fieldValue, boundary) {
36
+ const boundaryDelimiter = `--${boundary}`;
37
+ if (CR_OR_LF.test(fieldName)) {
38
+ throw new HttpProxyMiddlewareError(`[HPM] invalid multipart field name "${fieldName}" detected.`, `${HPM_ERR_INVALID_MULTIPART}_FIELD_NAME`);
39
+ }
40
+ if (CR_OR_LF.test(fieldValue) || fieldValue.includes(boundaryDelimiter)) {
41
+ throw new HttpProxyMiddlewareError(`[HPM] invalid multipart field value for "${fieldName}" detected.`, `${HPM_ERR_INVALID_MULTIPART}_FIELD_VALUE`);
42
+ }
43
+ }
44
+ function escapeMultipartFieldName(fieldName) {
45
+ return fieldName.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
46
+ }
@@ -0,0 +1,22 @@
1
+ import type * as http from 'node:http';
2
+ export type BodyParserLikeRequest = http.IncomingMessage & {
3
+ body?: any;
4
+ };
5
+ /**
6
+ * Fix proxied body if bodyParser is involved.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * createProxyMiddleware({
11
+ * target: 'http://example.com',
12
+ * on: {
13
+ * proxyReq: fixRequestBody,
14
+ * }
15
+ * });
16
+ * ```
17
+ *
18
+ * Alternative solution without using `fixRequestBody()`: put `http-proxy-middleware` before `bodyParser` in the middleware stack.
19
+ *
20
+ * @see {@link https://github.com/chimurai/http-proxy-middleware/issues/40 Github issue #40 - POST request body is not proxied}
21
+ */
22
+ export declare function fixRequestBody<TReq extends BodyParserLikeRequest = BodyParserLikeRequest>(proxyReq: http.ClientRequest, req: TReq): void;
@@ -0,0 +1,78 @@
1
+ import * as querystring from 'node:querystring';
2
+ import * as zlib from 'node:zlib';
3
+ import { stringifyFormData } from './fix-request-body-utils/stringify-form-data.js';
4
+ /**
5
+ * Fix proxied body if bodyParser is involved.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * createProxyMiddleware({
10
+ * target: 'http://example.com',
11
+ * on: {
12
+ * proxyReq: fixRequestBody,
13
+ * }
14
+ * });
15
+ * ```
16
+ *
17
+ * Alternative solution without using `fixRequestBody()`: put `http-proxy-middleware` before `bodyParser` in the middleware stack.
18
+ *
19
+ * @see {@link https://github.com/chimurai/http-proxy-middleware/issues/40 Github issue #40 - POST request body is not proxied}
20
+ */
21
+ export function fixRequestBody(proxyReq, req) {
22
+ // skip fixRequestBody() when req.readableLength not 0 (bodyParser failure)
23
+ if (req.readableLength !== 0) {
24
+ return;
25
+ }
26
+ const requestBody = req.body;
27
+ if (!requestBody) {
28
+ return;
29
+ }
30
+ const contentType = proxyReq.getHeader('Content-Type');
31
+ if (!contentType) {
32
+ return;
33
+ }
34
+ const writeBody = (bodyData) => {
35
+ let proxyData = bodyData;
36
+ const contentEncoding = String(proxyReq.getHeader('Content-Encoding')).toLowerCase();
37
+ switch (contentEncoding) {
38
+ case 'br':
39
+ proxyData = zlib.brotliCompressSync(proxyData);
40
+ break;
41
+ case 'deflate':
42
+ proxyData = zlib.deflateSync(proxyData);
43
+ break;
44
+ case 'gzip':
45
+ proxyData = zlib.gzipSync(proxyData);
46
+ break;
47
+ case 'zstd':
48
+ proxyData = zlib.zstdCompressSync(proxyData);
49
+ break;
50
+ }
51
+ proxyReq.setHeader('Content-Length', Buffer.byteLength(proxyData));
52
+ proxyReq.write(proxyData);
53
+ };
54
+ try {
55
+ // Use if-elseif to prevent multiple writeBody/setHeader calls:
56
+ // Error: "Cannot set headers after they are sent to the client"
57
+ if (contentType.includes('application/json') || contentType.includes('+json')) {
58
+ writeBody(JSON.stringify(requestBody));
59
+ }
60
+ else if (contentType.includes('application/x-www-form-urlencoded')) {
61
+ writeBody(querystring.stringify(requestBody));
62
+ }
63
+ else if (contentType.includes('multipart/form-data')) {
64
+ writeBody(stringifyFormData(contentType, requestBody));
65
+ }
66
+ else if (contentType.includes('text/plain')) {
67
+ writeBody(requestBody);
68
+ }
69
+ }
70
+ catch (error) {
71
+ // proxyReq listeners run outside the middleware try/catch path; re-throwing here can bubble as
72
+ // an unhandled exception in consumers, so destroy() is used to fail closed through proxy error handling.
73
+ proxyReq.destroy(toError(error));
74
+ }
75
+ }
76
+ function toError(error) {
77
+ return error instanceof Error ? error : new Error(String(error));
78
+ }
@@ -0,0 +1 @@
1
+ export * from './public.js';
@@ -0,0 +1 @@
1
+ export * from './public.js';
@@ -0,0 +1,2 @@
1
+ export { responseInterceptor } from './response-interceptor.js';
2
+ export { fixRequestBody } from './fix-request-body.js';
@@ -0,0 +1,2 @@
1
+ export { responseInterceptor } from './response-interceptor.js';
2
+ export { fixRequestBody } from './fix-request-body.js';
@@ -0,0 +1,27 @@
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
+ /**
4
+ * Intercept responses from upstream.
5
+ * Automatically decompress (deflate, gzip, brotli, zstd).
6
+ * Give developer the opportunity to modify intercepted Buffer and http.ServerResponse
7
+ *
8
+ * NOTE: must set options.selfHandleResponse=true (prevent automatic call of res.end())
9
+ *
10
+ * @example
11
+ *
12
+ * ```ts
13
+ * createProxyMiddleware({
14
+ * target: 'http://example.com',
15
+ * selfHandleResponse: true, // MUST set selfHandleResponse=true
16
+ * on: {
17
+ * proxyRes: responseInterceptor(async (buffer, proxyRes, req, res) => {
18
+ * // modify intercepted buffer and return modified buffer
19
+ * const modifiedBuffer = Buffer.from(buffer.toString().replace(/Example/g, 'Demo'), 'utf8');
20
+ * return modifiedBuffer;
21
+ * }),
22
+ * }
23
+ * });
24
+ * ```
25
+ */
26
+ 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>;
27
+ export {};
@@ -0,0 +1,145 @@
1
+ import * as zlib from 'node:zlib';
2
+ import { Debug } from '../debug.js';
3
+ import { getFunctionName } from '../utils/function.js';
4
+ const debug = Debug.extend('response-interceptor');
5
+ /**
6
+ * Intercept responses from upstream.
7
+ * Automatically decompress (deflate, gzip, brotli, zstd).
8
+ * Give developer the opportunity to modify intercepted Buffer and http.ServerResponse
9
+ *
10
+ * NOTE: must set options.selfHandleResponse=true (prevent automatic call of res.end())
11
+ *
12
+ * @example
13
+ *
14
+ * ```ts
15
+ * createProxyMiddleware({
16
+ * target: 'http://example.com',
17
+ * selfHandleResponse: true, // MUST set selfHandleResponse=true
18
+ * on: {
19
+ * proxyRes: responseInterceptor(async (buffer, proxyRes, req, res) => {
20
+ * // modify intercepted buffer and return modified buffer
21
+ * const modifiedBuffer = Buffer.from(buffer.toString().replace(/Example/g, 'Demo'), 'utf8');
22
+ * return modifiedBuffer;
23
+ * }),
24
+ * }
25
+ * });
26
+ * ```
27
+ */
28
+ export function responseInterceptor(interceptor) {
29
+ return async function proxyResResponseInterceptor(proxyRes, req, res) {
30
+ debug('intercept proxy response');
31
+ const originalProxyRes = proxyRes;
32
+ const chunks = [];
33
+ let bufferLength = 0;
34
+ // Bodyless responses (HEAD, 1xx, 204, 304) must not be decompressed.
35
+ const contentEncoding = isBodylessResponse(proxyRes.statusCode, req.method)
36
+ ? undefined
37
+ : proxyRes.headers['content-encoding'];
38
+ // decompress proxy response
39
+ const _proxyRes = decompress(proxyRes, contentEncoding);
40
+ // collect data chunks and concatenate once on end to avoid repeated full-buffer copies
41
+ _proxyRes.on('data', (chunk) => {
42
+ const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
43
+ chunks.push(chunkBuffer);
44
+ bufferLength += chunkBuffer.length; // precalculate Buffer length for slightly better performance on Buffer.concat()
45
+ });
46
+ _proxyRes.on('end', async () => {
47
+ const buffer = Buffer.concat(chunks, bufferLength);
48
+ chunks.length = 0; // clear chunks array
49
+ bufferLength = 0;
50
+ // copy original headers
51
+ copyHeaders(proxyRes, res);
52
+ // RFC 9110: HEAD and 1xx/204/304 responses do not include content.
53
+ // End the response after headers to avoid writing an invalid body.
54
+ if (isBodylessResponse(proxyRes.statusCode, req.method)) {
55
+ res.end();
56
+ return;
57
+ }
58
+ // call interceptor with intercepted response (buffer)
59
+ debug('call interceptor function: %s', getFunctionName(interceptor));
60
+ const interceptedBuffer = Buffer.from(await interceptor(buffer, originalProxyRes, req, res));
61
+ // set correct content-length (with double byte character support)
62
+ debug('set content-length: %s', Buffer.byteLength(interceptedBuffer));
63
+ // Buffered responses cannot preserve trailer framing.
64
+ // Remove trailer declaration (and transfer-encoding just in case) before setting content-length.
65
+ res.removeHeader('trailer');
66
+ res.removeHeader('transfer-encoding');
67
+ res.setHeader('content-length', Buffer.byteLength(interceptedBuffer));
68
+ debug('write intercepted response');
69
+ res.write(interceptedBuffer);
70
+ res.end();
71
+ });
72
+ _proxyRes.on('error', (error) => {
73
+ chunks.length = 0; // clear chunks array
74
+ bufferLength = 0;
75
+ res.end(`Error fetching proxied request: ${error.message}`);
76
+ });
77
+ };
78
+ }
79
+ function isBodylessResponse(statusCode, method) {
80
+ return (method?.toUpperCase() === 'HEAD' ||
81
+ (statusCode !== undefined &&
82
+ ((statusCode >= 100 && statusCode < 200) || statusCode === 204 || statusCode === 304)));
83
+ }
84
+ /**
85
+ * Streaming decompression of proxy response
86
+ * source: https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L116
87
+ */
88
+ function decompress(proxyRes, contentEncoding) {
89
+ let _proxyRes = proxyRes;
90
+ let decompress;
91
+ switch (contentEncoding) {
92
+ case 'gzip':
93
+ decompress = zlib.createGunzip();
94
+ break;
95
+ case 'br':
96
+ decompress = zlib.createBrotliDecompress();
97
+ break;
98
+ case 'deflate':
99
+ decompress = zlib.createInflate();
100
+ break;
101
+ case 'zstd':
102
+ decompress = zlib.createZstdDecompress();
103
+ break;
104
+ default:
105
+ break;
106
+ }
107
+ if (decompress) {
108
+ debug(`decompress proxy response with 'content-encoding': %s`, contentEncoding);
109
+ _proxyRes.pipe(decompress);
110
+ _proxyRes = decompress;
111
+ }
112
+ return _proxyRes;
113
+ }
114
+ /**
115
+ * Copy original headers
116
+ * https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L78
117
+ */
118
+ function copyHeaders(originalResponse, response) {
119
+ debug('copy original response headers');
120
+ if (originalResponse.statusCode) {
121
+ response.statusCode = originalResponse.statusCode;
122
+ }
123
+ if (originalResponse.statusMessage) {
124
+ response.statusMessage = originalResponse.statusMessage;
125
+ }
126
+ if (response.setHeader) {
127
+ let keys = Object.keys(originalResponse.headers);
128
+ // ignore encoding/framing headers that are incompatible with buffered interception
129
+ keys = keys.filter((key) => !['content-encoding', 'transfer-encoding', 'trailer'].includes(key));
130
+ keys.forEach((key) => {
131
+ let value = originalResponse.headers[key];
132
+ if (key === 'set-cookie' && value) {
133
+ // remove cookie domain
134
+ value = Array.isArray(value) ? value : [value];
135
+ value = value.map((x) => x.replace(/Domain=[^;]+?/i, ''));
136
+ }
137
+ response.setHeader(key, value);
138
+ });
139
+ }
140
+ else {
141
+ if ('headers' in response) {
142
+ response.headers = originalResponse.headers;
143
+ }
144
+ }
145
+ }