This commit is contained in:
2022-07-18 02:50:52 +00:00
parent befd344ab0
commit 06181b34d6
8569 changed files with 818704 additions and 352705 deletions
+4
View File
@@ -0,0 +1,4 @@
import type { Options } from './types';
import type * as httpProxy from 'http-proxy';
export declare function init(proxy: httpProxy, option: Options): void;
export declare function getHandlers(options: Options): any;
+74
View File
@@ -0,0 +1,74 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getHandlers = exports.init = void 0;
const logger_1 = require("./logger");
const logger = logger_1.getInstance();
function init(proxy, option) {
const handlers = getHandlers(option);
for (const eventName of Object.keys(handlers)) {
proxy.on(eventName, handlers[eventName]);
}
logger.debug('[HPM] Subscribed to http-proxy events:', Object.keys(handlers));
}
exports.init = init;
function getHandlers(options) {
// https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events
const proxyEventsMap = {
error: 'onError',
proxyReq: 'onProxyReq',
proxyReqWs: 'onProxyReqWs',
proxyRes: 'onProxyRes',
open: 'onOpen',
close: 'onClose',
};
const handlers = {};
for (const [eventName, onEventName] of Object.entries(proxyEventsMap)) {
// all handlers for the http-proxy events are prefixed with 'on'.
// loop through options and try to find these handlers
// and add them to the handlers object for subscription in init().
const fnHandler = options ? options[onEventName] : null;
if (typeof fnHandler === 'function') {
handlers[eventName] = fnHandler;
}
}
// add default error handler in absence of error handler
if (typeof handlers.error !== 'function') {
handlers.error = defaultErrorHandler;
}
// add default close handler in absence of close handler
if (typeof handlers.close !== 'function') {
handlers.close = logClose;
}
return handlers;
}
exports.getHandlers = getHandlers;
function defaultErrorHandler(err, req, res) {
// Re-throw error. Not recoverable since req & res are empty.
if (!req && !res) {
throw err; // "Error: Must provide a proper URL as target"
}
const host = req.headers && req.headers.host;
const code = err.code;
if (res.writeHead && !res.headersSent) {
if (/HPE_INVALID/.test(code)) {
res.writeHead(502);
}
else {
switch (code) {
case 'ECONNRESET':
case 'ENOTFOUND':
case 'ECONNREFUSED':
case 'ETIMEDOUT':
res.writeHead(504);
break;
default:
res.writeHead(500);
}
}
}
res.end(`Error occured while trying to proxy: ${host}${req.url}`);
}
function logClose(req, socket, head) {
// view disconnected websocket connections
logger.info('[HPM] Client disconnected');
}
+4 -3
View File
@@ -1,5 +1,6 @@
import { Options } from './types';
export declare function createConfig(context: any, opts?: any): {
context: any;
import { Filter, Options } from './types';
export declare type Config = {
context: Filter;
options: Options;
};
export declare function createConfig(context: any, opts?: Options): Config;
+7 -7
View File
@@ -1,7 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createConfig = void 0;
const _ = require("lodash");
const isPlainObj = require("is-plain-obj");
const url = require("url");
const errors_1 = require("./errors");
const logger_1 = require("./logger");
@@ -15,7 +15,7 @@ function createConfig(context, opts) {
// app.use('/api', proxy({target:'http://localhost:9000'}));
if (isContextless(context, opts)) {
config.context = '/';
config.options = _.assign(config.options, context);
config.options = Object.assign(config.options, context);
// app.use('/api', proxy('http://localhost:9000'));
// app.use(proxy('http://localhost:9000/api'));
}
@@ -23,7 +23,7 @@ function createConfig(context, opts) {
const oUrl = url.parse(context);
const target = [oUrl.protocol, '//', oUrl.host].join('');
config.context = oUrl.pathname || '/';
config.options = _.assign(config.options, { target }, opts);
config.options = Object.assign(config.options, { target }, opts);
if (oUrl.protocol === 'ws:' || oUrl.protocol === 'wss:') {
config.options.ws = true;
}
@@ -31,10 +31,10 @@ function createConfig(context, opts) {
}
else {
config.context = context;
config.options = _.assign(config.options, opts);
config.options = Object.assign(config.options, opts);
}
configureLogger(config.options);
if (!config.options.target) {
if (!config.options.target && !config.options.router) {
throw new Error(errors_1.ERRORS.ERR_CONFIG_FACTORY_TARGET_MISSING);
}
return config;
@@ -52,7 +52,7 @@ exports.createConfig = createConfig;
* @return {Boolean} [description]
*/
function isStringShortHand(context) {
if (_.isString(context)) {
if (typeof context === 'string') {
return !!url.parse(context).host;
}
}
@@ -68,7 +68,7 @@ function isStringShortHand(context) {
* @return {Boolean} [description]
*/
function isContextless(context, opts) {
return _.isPlainObject(context) && _.isEmpty(opts);
return isPlainObj(context) && (opts == null || Object.keys(opts).length === 0);
}
function configureLogger(options) {
if (options.logLevel) {
+2 -1
View File
@@ -1 +1,2 @@
export declare function match(context: any, uri: any, req: any): any;
import type { Filter, Request } from './types';
export declare function match(context: Filter, uri: string, req: Request): boolean;
+2 -3
View File
@@ -2,7 +2,6 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.match = void 0;
const isGlob = require("is-glob");
const _ = require("lodash");
const micromatch = require("micromatch");
const url = require("url");
const errors_1 = require("./errors");
@@ -26,7 +25,7 @@ function match(context, uri, req) {
throw new Error(errors_1.ERRORS.ERR_CONTEXT_MATCHER_INVALID_ARRAY);
}
// custom matching
if (_.isFunction(context)) {
if (typeof context === 'function') {
const pathname = getUrlPathName(uri);
return context(pathname, req);
}
@@ -75,7 +74,7 @@ function getUrlPathName(uri) {
return uri && url.parse(uri).pathname;
}
function isStringPath(context) {
return _.isString(context) && !isGlob(context);
return typeof context === 'string' && !isGlob(context);
}
function isGlobPath(context) {
return isGlob(context);
@@ -0,0 +1,7 @@
/// <reference types="node" />
import { ClientRequest } from 'http';
import type { Request } from '../types';
/**
* Fix proxied body if bodyParser is involved.
*/
export declare function fixRequestBody(proxyReq: ClientRequest, req: Request): void;
+25
View File
@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fixRequestBody = void 0;
const querystring = require("querystring");
/**
* Fix proxied body if bodyParser is involved.
*/
function fixRequestBody(proxyReq, req) {
if (!req.body || !Object.keys(req.body).length) {
return;
}
const contentType = proxyReq.getHeader('Content-Type');
const writeBody = (bodyData) => {
// deepcode ignore ContentLengthInCode: bodyParser fix
proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData));
proxyReq.write(bodyData);
};
if (contentType && contentType.includes('application/json')) {
writeBody(JSON.stringify(req.body));
}
if (contentType === 'application/x-www-form-urlencoded') {
writeBody(querystring.stringify(req.body));
}
}
exports.fixRequestBody = fixRequestBody;
+1
View File
@@ -0,0 +1 @@
export * from './public';
+13
View File
@@ -0,0 +1,13 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./public"), exports);
+2
View File
@@ -0,0 +1,2 @@
export { responseInterceptor } from './response-interceptor';
export { fixRequestBody } from './fix-request-body';
+7
View File
@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fixRequestBody = exports.responseInterceptor = void 0;
var response_interceptor_1 = require("./response-interceptor");
Object.defineProperty(exports, "responseInterceptor", { enumerable: true, get: function () { return response_interceptor_1.responseInterceptor; } });
var fix_request_body_1 = require("./fix-request-body");
Object.defineProperty(exports, "fixRequestBody", { enumerable: true, get: function () { return fix_request_body_1.fixRequestBody; } });
@@ -0,0 +1,12 @@
/// <reference types="node" />
import type * as http from 'http';
declare type Interceptor = (buffer: Buffer, proxyRes: http.IncomingMessage, req: http.IncomingMessage, res: http.ServerResponse) => Promise<Buffer | string>;
/**
* Intercept responses from upstream.
* Automatically decompress (deflate, gzip, brotli).
* Give developer the opportunity to modify intercepted Buffer and http.ServerResponse
*
* NOTE: must set options.selfHandleResponse=true (prevent automatic call of res.end())
*/
export declare function responseInterceptor(interceptor: Interceptor): (proxyRes: http.IncomingMessage, req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>;
export {};
@@ -0,0 +1,97 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.responseInterceptor = void 0;
const zlib = require("zlib");
/**
* Intercept responses from upstream.
* Automatically decompress (deflate, gzip, brotli).
* Give developer the opportunity to modify intercepted Buffer and http.ServerResponse
*
* NOTE: must set options.selfHandleResponse=true (prevent automatic call of res.end())
*/
function responseInterceptor(interceptor) {
return function proxyRes(proxyRes, req, res) {
return __awaiter(this, void 0, void 0, function* () {
const originalProxyRes = proxyRes;
let buffer = Buffer.from('', 'utf8');
// decompress proxy response
const _proxyRes = decompress(proxyRes, proxyRes.headers['content-encoding']);
// concat data stream
_proxyRes.on('data', (chunk) => (buffer = Buffer.concat([buffer, chunk])));
_proxyRes.on('end', () => __awaiter(this, void 0, void 0, function* () {
// copy original headers
copyHeaders(proxyRes, res);
// call interceptor with intercepted response (buffer)
const interceptedBuffer = Buffer.from(yield interceptor(buffer, originalProxyRes, req, res));
// set correct content-length (with double byte character support)
res.setHeader('content-length', Buffer.byteLength(interceptedBuffer, 'utf8'));
res.write(interceptedBuffer);
res.end();
}));
_proxyRes.on('error', (error) => {
res.end(`Error fetching proxied request: ${error.message}`);
});
});
};
}
exports.responseInterceptor = responseInterceptor;
/**
* Streaming decompression of proxy response
* source: https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L116
*/
function decompress(proxyRes, contentEncoding) {
let _proxyRes = proxyRes;
let decompress;
switch (contentEncoding) {
case 'gzip':
decompress = zlib.createGunzip();
break;
case 'br':
decompress = zlib.createBrotliDecompress();
break;
case 'deflate':
decompress = zlib.createInflate();
break;
default:
break;
}
if (decompress) {
_proxyRes.pipe(decompress);
_proxyRes = decompress;
}
return _proxyRes;
}
/**
* Copy original headers
* https://github.com/apache/superset/blob/9773aba522e957ed9423045ca153219638a85d2f/superset-frontend/webpack.proxy-config.js#L78
*/
function copyHeaders(originalResponse, response) {
response.statusCode = originalResponse.statusCode;
response.statusMessage = originalResponse.statusMessage;
if (response.setHeader) {
let keys = Object.keys(originalResponse.headers);
// ignore chunked, brotli, gzip, deflate headers
keys = keys.filter((key) => !['content-encoding', 'transfer-encoding'].includes(key));
keys.forEach((key) => {
let value = originalResponse.headers[key];
if (key === 'set-cookie') {
// remove cookie domain
value = Array.isArray(value) ? value : [value];
value = value.map((x) => x.replace(/Domain=[^;]+?/i, ''));
}
response.setHeader(key, value);
});
}
else {
response.headers = originalResponse.headers;
}
}
+2 -1
View File
@@ -1,8 +1,9 @@
import { Filter, RequestHandler, Options } from './types';
import type { Filter, RequestHandler, Options } from './types';
export declare class HttpProxyMiddleware {
private logger;
private config;
private wsInternalSubscribed;
private serverOnCloseSubscribed;
private proxyOptions;
private proxy;
private pathRewriter;
+28 -9
View File
@@ -11,10 +11,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpProxyMiddleware = void 0;
const httpProxy = require("http-proxy");
const _ = require("lodash");
const config_factory_1 = require("./config-factory");
const contextMatcher = require("./context-matcher");
const handlers = require("./handlers");
const handlers = require("./_handlers");
const logger_1 = require("./logger");
const PathRewriter = require("./path-rewriter");
const Router = require("./router");
@@ -22,8 +21,10 @@ class HttpProxyMiddleware {
constructor(context, opts) {
this.logger = logger_1.getInstance();
this.wsInternalSubscribed = false;
this.serverOnCloseSubscribed = false;
// https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript#red-flags-for-this
this.middleware = (req, res, next) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (this.shouldProxy(this.config.context, req)) {
try {
const activeProxyOptions = yield this.prepareProxyRequest(req);
@@ -36,9 +37,25 @@ class HttpProxyMiddleware {
else {
next();
}
/**
* Get the server object to subscribe to server events;
* 'upgrade' for websocket and 'close' for graceful shutdown
*
* NOTE:
* req.socket: node >= 13
* req.connection: node < 13 (Remove this when node 12/13 support is dropped)
*/
const server = (_b = ((_a = req.socket) !== null && _a !== void 0 ? _a : req.connection)) === null || _b === void 0 ? void 0 : _b.server;
if (server && !this.serverOnCloseSubscribed) {
server.on('close', () => {
this.logger.info('[HPM] server close signal received: closing proxy server');
this.proxy.close();
});
this.serverOnCloseSubscribed = true;
}
if (this.proxyOptions.ws === true) {
// use initial request to access the server object to subscribe to http upgrade event
this.catchUpgradeRequest(req.connection.server);
this.catchUpgradeRequest(server);
}
});
this.catchUpgradeRequest = (server) => {
@@ -82,7 +99,7 @@ class HttpProxyMiddleware {
req.url = req.originalUrl || req.url;
// store uri before it gets rewritten for logging
const originalPath = req.url;
const newProxyOptions = _.assign({}, this.proxyOptions);
const newProxyOptions = Object.assign({}, this.proxyOptions);
// Apply in order:
// 1. option.router
// 2. option.pathRewrite
@@ -118,12 +135,14 @@ class HttpProxyMiddleware {
}
}
});
this.logError = (err, req, res) => {
const hostname = (req.headers && req.headers.host) || req.hostname || req.host; // (websocket) || (node0.10 || node 4/5)
const target = this.proxyOptions.target.host || this.proxyOptions.target;
const errorMessage = '[HPM] Error occurred while trying to proxy request %s from %s to %s (%s) (%s)';
this.logError = (err, req, res, target) => {
var _a;
const hostname = ((_a = req.headers) === null || _a === void 0 ? void 0 : _a.host) || req.hostname || req.host; // (websocket) || (node0.10 || node 4/5)
const requestHref = `${hostname}${req.url}`;
const targetHref = `${target === null || target === void 0 ? void 0 : target.href}`; // target is undefined when websocket errors
const errorMessage = '[HPM] Error occurred while proxying request %s to %s [%s] (%s)';
const errReference = 'https://nodejs.org/api/errors.html#errors_common_system_errors'; // link to Node Common Systems Errors page
this.logger.error(errorMessage, req.url, hostname, target, err.code || err, errReference);
this.logger.error(errorMessage, requestHref, targetHref, err.code || err, errReference);
};
this.config = config_factory_1.createConfig(context, opts);
this.proxyOptions = this.config.options;
+1
View File
@@ -1,3 +1,4 @@
import { Filter, Options } from './types';
export declare function createProxyMiddleware(context: Filter | Options, options?: Options): import("./types").RequestHandler;
export * from './handlers';
export { Filter, Options, RequestHandler } from './types';
+11
View File
@@ -1,4 +1,14 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createProxyMiddleware = void 0;
const http_proxy_middleware_1 = require("./http-proxy-middleware");
@@ -7,3 +17,4 @@ function createProxyMiddleware(context, options) {
return middleware;
}
exports.createProxyMiddleware = createProxyMiddleware;
__exportStar(require("./handlers"), exports);
+4 -5
View File
@@ -1,7 +1,7 @@
"use strict";
/* eslint-disable prefer-rest-params */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getArrow = exports.getInstance = void 0;
const _ = require("lodash");
const util = require("util");
let loggerInstance;
const defaultProvider = {
@@ -69,7 +69,7 @@ class Logger {
}
isValidProvider(fnProvider) {
const result = true;
if (fnProvider && !_.isFunction(fnProvider)) {
if (fnProvider && typeof fnProvider !== 'function') {
throw new Error('[HPM] Log provider config error. Expecting a function.');
}
return result;
@@ -97,9 +97,8 @@ class Logger {
}
// make sure logged messages and its data are return interpolated
// make it possible for additional log data, such date/time or custom prefix.
_interpolate() {
const fn = _.spread(util.format);
const result = fn(_.slice(arguments));
_interpolate(format, ...args) {
const result = util.format(format, ...args);
return result;
}
}
+1 -1
View File
@@ -4,4 +4,4 @@
* @param {Object} rewriteConfig
* @return {Function} Function to rewrite paths; This function should accept `path` (request.url) as parameter
*/
export declare function createPathRewriter(rewriteConfig: any): (...args: any[]) => any;
export declare function createPathRewriter(rewriteConfig: any): any;
+12 -14
View File
@@ -1,7 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPathRewriter = void 0;
const _ = require("lodash");
const isPlainObj = require("is-plain-obj");
const errors_1 = require("./errors");
const logger_1 = require("./logger");
const logger = logger_1.getInstance();
@@ -16,7 +16,7 @@ function createPathRewriter(rewriteConfig) {
if (!isValidRewriteConfig(rewriteConfig)) {
return;
}
if (_.isFunction(rewriteConfig)) {
if (typeof rewriteConfig === 'function') {
const customRewriteFn = rewriteConfig;
return customRewriteFn;
}
@@ -26,27 +26,25 @@ function createPathRewriter(rewriteConfig) {
}
function rewritePath(path) {
let result = path;
_.forEach(rulesCache, (rule) => {
for (const rule of rulesCache) {
if (rule.regex.test(path)) {
result = result.replace(rule.regex, rule.value);
logger.debug('[HPM] Rewriting path from "%s" to "%s"', path, result);
return false;
break;
}
});
}
return result;
}
}
exports.createPathRewriter = createPathRewriter;
function isValidRewriteConfig(rewriteConfig) {
if (_.isFunction(rewriteConfig)) {
if (typeof rewriteConfig === 'function') {
return true;
}
else if (!_.isEmpty(rewriteConfig) && _.isPlainObject(rewriteConfig)) {
return true;
else if (isPlainObj(rewriteConfig)) {
return Object.keys(rewriteConfig).length !== 0;
}
else if (_.isUndefined(rewriteConfig) ||
_.isNull(rewriteConfig) ||
_.isEqual(rewriteConfig, {})) {
else if (rewriteConfig === undefined || rewriteConfig === null) {
return false;
}
else {
@@ -55,14 +53,14 @@ function isValidRewriteConfig(rewriteConfig) {
}
function parsePathRewriteRules(rewriteConfig) {
const rules = [];
if (_.isPlainObject(rewriteConfig)) {
_.forIn(rewriteConfig, (value, key) => {
if (isPlainObj(rewriteConfig)) {
for (const [key] of Object.entries(rewriteConfig)) {
rules.push({
regex: new RegExp(key),
value: rewriteConfig[key],
});
logger.info('[HPM] Proxy rewrite rule created: "%s" ~> "%s"', key, rewriteConfig[key]);
});
}
}
return rules;
}
+7 -7
View File
@@ -10,17 +10,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTarget = void 0;
const _ = require("lodash");
const isPlainObj = require("is-plain-obj");
const logger_1 = require("./logger");
const logger = logger_1.getInstance();
function getTarget(req, config) {
return __awaiter(this, void 0, void 0, function* () {
let newTarget;
const router = config.router;
if (_.isPlainObject(router)) {
if (isPlainObj(router)) {
newTarget = getTargetFromProxyTable(req, router);
}
else if (_.isFunction(router)) {
else if (typeof router === 'function') {
newTarget = yield router(req);
}
return newTarget;
@@ -32,13 +32,13 @@ function getTargetFromProxyTable(req, table) {
const host = req.headers.host;
const path = req.url;
const hostAndPath = host + path;
_.forIn(table, (value, key) => {
for (const [key] of Object.entries(table)) {
if (containsPath(key)) {
if (hostAndPath.indexOf(key) > -1) {
// match 'localhost:3000/api'
result = table[key];
logger.debug('[HPM] Router table match: "%s"', key);
return false;
break;
}
}
else {
@@ -46,10 +46,10 @@ function getTargetFromProxyTable(req, table) {
// match 'localhost:3000'
result = table[key];
logger.debug('[HPM] Router table match: "%s"', host);
return false;
break;
}
}
});
}
return result;
}
function containsPath(v) {
+1
View File
@@ -1,2 +1,3 @@
"use strict";
/* eslint-disable @typescript-eslint/no-empty-interface */
Object.defineProperty(exports, "__esModule", { value: true });