diff --git a/package.json b/package.json index 7d4b0505..179a49d9 100644 --- a/package.json +++ b/package.json @@ -89,9 +89,8 @@ "ws": "8.19.0" }, "dependencies": { - "@types/http-proxy": "^1.17.15", "debug": "^4.3.6", - "http-proxy": "^1.18.1", + "http-proxy-3": "^1.23.2", "is-glob": "^4.0.3", "is-plain-object": "^5.0.0", "micromatch": "^4.0.8" diff --git a/patches/http-proxy+1.18.1.patch b/patches/http-proxy+1.18.1.patch deleted file mode 100644 index d3fecc15..00000000 --- a/patches/http-proxy+1.18.1.patch +++ /dev/null @@ -1,23 +0,0 @@ -diff --git a/node_modules/http-proxy/lib/http-proxy/common.js b/node_modules/http-proxy/lib/http-proxy/common.js -index 6513e81..d01d8db 100644 ---- a/node_modules/http-proxy/lib/http-proxy/common.js -+++ b/node_modules/http-proxy/lib/http-proxy/common.js -@@ -1,6 +1,6 @@ - var common = exports, - url = require('url'), -- extend = require('util')._extend, -+ extend = Object.assign, - required = require('requires-port'); - - var upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i, -diff --git a/node_modules/http-proxy/lib/http-proxy/index.js b/node_modules/http-proxy/lib/http-proxy/index.js -index 977a4b3..739409c 100644 ---- a/node_modules/http-proxy/lib/http-proxy/index.js -+++ b/node_modules/http-proxy/lib/http-proxy/index.js -@@ -1,5 +1,5 @@ - var httpProxy = module.exports, -- extend = require('util')._extend, -+ extend = Object.assign, - parse_url = require('url').parse, - EE3 = require('eventemitter3'), - http = require('http'), diff --git a/src/http-proxy-middleware.ts b/src/http-proxy-middleware.ts index 6fe301c0..9d74f5ac 100644 --- a/src/http-proxy-middleware.ts +++ b/src/http-proxy-middleware.ts @@ -2,7 +2,7 @@ import type * as http from 'node:http'; import type * as https from 'node:https'; import type * as net from 'node:net'; -import * as httpProxy from 'http-proxy'; +import * as httpProxy from 'http-proxy-3'; import { verifyConfig } from './configuration'; import { Debug as debug } from './debug'; @@ -18,7 +18,7 @@ export class HttpProxyMiddleware { private wsInternalSubscribed = false; private serverOnCloseSubscribed = false; private proxyOptions: Options; - private proxy: httpProxy; + private proxy: httpProxy.ProxyServer; private pathRewriter; private logger: Logger; @@ -81,7 +81,7 @@ export class HttpProxyMiddleware { } }) as RequestHandler; - private registerPlugins(proxy: httpProxy, options: Options) { + private registerPlugins(proxy: httpProxy.ProxyServer, options: Options) { const plugins = getPlugins(options); plugins.forEach((plugin) => { debug(`register plugin: "${getFunctionName(plugin)}"`); @@ -109,7 +109,8 @@ export class HttpProxyMiddleware { } catch (err) { // This error does not include the URL as the fourth argument as we won't // have the URL if `this.prepareProxyRequest` throws an error. - this.proxy.emit('error', err, req, socket); + + this.proxy.emit('error', err as Error, req, socket); } }; @@ -155,6 +156,10 @@ export class HttpProxyMiddleware { await this.applyRouter(req, newProxyOptions); await this.applyPathRewrite(req, this.pathRewriter); + if (!newProxyOptions.target) { + throw new Error('Must provide a proper URL as target'); + } + return newProxyOptions; }; diff --git a/src/plugins/default/proxy-events.ts b/src/plugins/default/proxy-events.ts index aa686d90..94e663fd 100644 --- a/src/plugins/default/proxy-events.ts +++ b/src/plugins/default/proxy-events.ts @@ -27,6 +27,6 @@ const debug = Debug.extend('proxy-events-plugin'); export const proxyEventsPlugin: Plugin = (proxyServer, options) => { Object.entries(options.on || {}).forEach(([eventName, handler]) => { debug(`register event handler: "${eventName}" -> "${getFunctionName(handler)}"`); - proxyServer.on(eventName, handler as (...args: unknown[]) => void); + proxyServer.on(eventName, handler as never); }); }; diff --git a/src/types.ts b/src/types.ts index dbddb4ec..7a8968ed 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,10 +5,52 @@ import type * as http from 'node:http'; import type * as net from 'node:net'; -import type * as httpProxy from 'http-proxy'; +import type { ErrorCallback, ProxyServer, ServerOptions } from 'http-proxy-3'; export type NextFunction void> = T; +export interface Plugin { + (proxyServer: ProxyServer, options: Options): void; +} + +export interface OnProxyEvent { + error?: ( + err: Error, + req: TReq, + res: TRes, + target: string | { port: number; host: string; protocol?: string }, + ) => void | ErrorCallback; + proxyReq?: ( + proxyReq: http.ClientRequest, + req: TReq, + res: TRes, + options: ServerOptions, + socket: net.Socket, + ) => void; + proxyReqWs?: ( + proxyReq: http.ClientRequest, + req: TReq, + socket: net.Socket, + options: ServerOptions, + head: any, + ) => void; + proxyRes?: (proxyRes: TReq, req: TReq, res: TRes) => void; + open?: (socket: net.Socket) => void; + close?: (proxyRes: TReq, proxySocket: net.Socket, proxyHead: any) => void; + start?: ( + req: TReq, + res: TRes, + target: string | { port: number; host: string; protocol?: string }, + ) => void; + end?: (req: TReq, res: TRes, proxyRes: TReq) => void; + econnreset?: ( + err: Error, + req: TReq, + res: TRes, + target: string | { port: number; host: string; protocol?: string }, + ) => void; +} + export interface RequestHandler< TReq = http.IncomingMessage, TRes = http.ServerResponse, @@ -23,26 +65,12 @@ export type Filter = | string[] | ((pathname: string, req: TReq) => boolean); -export interface Plugin { - (proxyServer: httpProxy, options: Options): void; -} - -export interface OnProxyEvent { - error?: httpProxy.ErrorCallback; - proxyReq?: httpProxy.ProxyReqCallback; - proxyReqWs?: httpProxy.ProxyReqWsCallback; - proxyRes?: httpProxy.ProxyResCallback; - open?: httpProxy.OpenCallback; - close?: httpProxy.CloseCallback; - start?: httpProxy.StartCallback; - end?: httpProxy.EndCallback; - econnreset?: httpProxy.EconnresetCallback; -} - export type Logger = Pick; -export interface Options - extends httpProxy.ServerOptions { +export interface Options< + TReq = http.IncomingMessage, + TRes = http.ServerResponse, +> extends ServerOptions { /** * Narrow down requests to proxy or not. * Filter on {@link http.IncomingMessage.url `pathname`} which is relative to the proxy's "mounting" point in the server. @@ -122,9 +150,9 @@ export interface Options httpProxy.ServerOptions['target']) - | ((req: TReq) => Promise); + | { [hostOrPath: string]: ServerOptions['target'] } + | ((req: TReq) => ServerOptions['target']) + | ((req: TReq) => Promise); /** * Log information from http-proxy-middleware * @example diff --git a/test/e2e/express-error-middleware.spec.ts b/test/e2e/express-error-middleware.spec.ts index 8fe47a3c..69f967c4 100644 --- a/test/e2e/express-error-middleware.spec.ts +++ b/test/e2e/express-error-middleware.spec.ts @@ -1,3 +1,4 @@ +import * as express from 'express'; import * as request from 'supertest'; import { createApp, createProxyMiddleware } from './test-kit'; @@ -11,7 +12,12 @@ describe('express error middleware', () => { router: (req) => undefined, // Trigger "Error: Must provide a proper URL as target" }); - const errorMiddleware = (err, req, res, next) => { + const errorMiddleware = ( + err: Error, + req: express.Request, + res: express.Response, + next: express.NextFunction, + ) => { httpProxyError = err; res.status(504).send('Something broke!'); }; diff --git a/test/e2e/express-router.spec.ts b/test/e2e/express-router.spec.ts index fc682665..c428cd56 100644 --- a/test/e2e/express-router.spec.ts +++ b/test/e2e/express-router.spec.ts @@ -1,4 +1,5 @@ import * as express from 'express'; +import * as http from 'http'; import * as request from 'supertest'; import { Options } from '../../src/index'; @@ -18,7 +19,7 @@ describe('Usage in Express', () => { // sub route config const sub = express.Router(); - function filter(pathname, req) { + function filter(pathname: string, req: http.IncomingMessage) { const urlFilter = new RegExp('^/sub/api'); const match = urlFilter.test(pathname); return match; @@ -49,8 +50,8 @@ describe('Usage in Express', () => { }); }); - function jsonMiddleware(data) { - return (req, res) => { + function jsonMiddleware(data: any) { + return (req: express.Request, res: express.Response) => { res.json(data); }; } diff --git a/test/e2e/http-proxy-middleware.spec.ts b/test/e2e/http-proxy-middleware.spec.ts index 7e167e40..70a9a7f0 100644 --- a/test/e2e/http-proxy-middleware.spec.ts +++ b/test/e2e/http-proxy-middleware.spec.ts @@ -133,7 +133,7 @@ describe('E2E http-proxy-middleware', () => { describe('custom pathFilter matcher/filter', () => { it('should have response body: "HELLO WEB"', async () => { - const filter = (path, req) => { + const filter = (path: string, req: http.IncomingMessage) => { return true; }; @@ -152,7 +152,7 @@ describe('E2E http-proxy-middleware', () => { }); it('should not proxy when filter returns false', async () => { - const filter = (path, req) => { + const filter = (path: string, req: http.IncomingMessage) => { return false; }; @@ -172,7 +172,7 @@ describe('E2E http-proxy-middleware', () => { it('should not proxy when filter throws Error', async () => { const myError = new Error('MY_ERROR'); - const filter = (path, req) => { + const filter = (path: string, req: http.IncomingMessage) => { throw myError; }; diff --git a/test/e2e/router.spec.ts b/test/e2e/router.spec.ts index b7206ab7..58db8edb 100644 --- a/test/e2e/router.spec.ts +++ b/test/e2e/router.spec.ts @@ -2,6 +2,7 @@ import { ErrorRequestHandler } from 'express'; import * as getPort from 'get-port'; import { Mockttp, generateCACertificate, getLocal } from 'mockttp'; import * as request from 'supertest'; +import * as TestAgent from 'supertest'; import { createApp, createAppWithPath, createProxyMiddleware } from './test-kit'; @@ -147,7 +148,7 @@ describe('E2E router', () => { }); describe('router with proxyTable', () => { - let agent; + let agent: TestAgent.Agent; beforeEach(() => { const app = createAppWithPath( diff --git a/test/e2e/test-kit.ts b/test/e2e/test-kit.ts index d1e857f4..89e326b9 100644 --- a/test/e2e/test-kit.ts +++ b/test/e2e/test-kit.ts @@ -3,7 +3,7 @@ import type { Express, RequestHandler } from 'express'; export { createProxyMiddleware, responseInterceptor, fixRequestBody } from '../../src/index'; -export function createApp(...middlewares): Express { +export function createApp(...middlewares: any[]): Express { const app = express(); app.use(...middlewares); return app; diff --git a/test/e2e/websocket.spec.ts b/test/e2e/websocket.spec.ts index f2408cfb..e82b3f91 100644 --- a/test/e2e/websocket.spec.ts +++ b/test/e2e/websocket.spec.ts @@ -54,7 +54,7 @@ describe('E2E WebSocket proxy', () => { proxyServer = createApp(proxyMiddleware).listen(SERVER_PORT); // quick & dirty Promise version of http.get (don't care about correctness) - const get = async (uri) => new Promise((resolve, reject) => http.get(uri, resolve)); + const get = async (uri: string) => new Promise((resolve, reject) => http.get(uri, resolve)); // need to make a normal http request, so http-proxy-middleware can catch the upgrade request await get(`http://localhost:${SERVER_PORT}/`); diff --git a/yarn.lock b/yarn.lock index 98f3703a..bd0c795b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1412,13 +1412,6 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== -"@types/http-proxy@^1.17.15": - version "1.17.15" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.15.tgz#12118141ce9775a6499ecb4c01d02f90fc839d36" - integrity sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ== - dependencies: - "@types/node" "*" - "@types/is-glob@4.0.4": version "4.0.4" resolved "https://registry.yarnpkg.com/@types/is-glob/-/is-glob-4.0.4.tgz#1d60fa47ff70abc97b4d9ea45328747c488b3a50" @@ -2835,11 +2828,6 @@ eventemitter3@^3.1.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - eventemitter3@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" @@ -3092,10 +3080,10 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== -follow-redirects@^1.0.0: - version "1.15.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== +follow-redirects@^1.15.9: + version "1.15.11" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== foreground-child@^3.1.0: version "3.3.1" @@ -3409,6 +3397,14 @@ http-errors@~2.0.1: statuses "~2.0.2" toidentifier "~1.0.1" +http-proxy-3@^1.23.2: + version "1.23.2" + resolved "https://registry.yarnpkg.com/http-proxy-3/-/http-proxy-3-1.23.2.tgz#c3cceead89ce94e8feca7f75bb23e508f3924347" + integrity sha512-vZks1dLliM0w7aQDT9eFYLO8PUuQ9Cm67y7kn+kgkLtvKP0HZ6Thb3+MCGFFNCnKMCkLXY6rvIH1d7jQITryxA== + dependencies: + debug "^4.4.0" + follow-redirects "^1.15.9" + http-proxy-agent@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" @@ -3417,15 +3413,6 @@ http-proxy-agent@^7.0.0: agent-base "^7.1.0" debug "^4.3.4" -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - http2-wrapper@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-2.2.1.tgz#310968153dcdedb160d8b72114363ef5fce1f64a" @@ -5106,11 +5093,6 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - resolve-alpn@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"