diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 00000000..547819d7 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,163 @@ +# Migration guide + +- [v2 to v3 adapter](#v2-to-v3-adapter) + - [`legacyCreateProxyMiddleware`](#legacycreateproxymiddleware) +- [v3 breaking changes](#v3-breaking-changes) + - [Removed `req.url` patching](#removed-requrl-patching) + - [`pathRewrite` (potential behavior change)](#pathrewrite-potential-behavior-change) + - [Removed "shorthand" usage](#removed-shorthand-usage) + - [Removed `context` argument](#removed-context-argument) + - [Removed `logProvider` and `logLevel` options](#removed-logprovider-and-loglevel-options) + - [Refactored proxy events](#refactored-proxy-events) + +## v2 to v3 adapter + +### `legacyCreateProxyMiddleware` + +Use the adapter to use v3 with minimal changes to your v2 implementation. + +NOTE: `legacyCreateProxyMiddleware` will be removed in a future version. + +```js +// before +const { createProxyMiddleware } = require('http-proxy-middleware'); + +createProxyMiddleware(...); + +// after +const { legacyCreateProxyMiddleware } = require('http-proxy-middleware'); + +legacyCreateProxyMiddleware(...); +``` + +```ts +// before +import { createProxyMiddleware, Options } from 'http-proxy-middleware'; + +createProxyMiddleware(...); + +// after +import { legacyCreateProxyMiddleware, LegacyOptions } from 'http-proxy-middleware'; + +legacyCreateProxyMiddleware(...); +``` + +## v3 breaking changes + +### Removed `req.url` patching + +```js +// before +app.use('/user', proxy({ target: 'http://www.example.org' })); + +// after +app.use('/user', proxy({ target: 'http://www.example.org/user' })); +``` + +### `pathRewrite` (potential behavior change) + +Related to removal of [`req.url` patching](#removed-requrl-patching). + +`pathRewrite` now only rewrites the `path` after the mount point. + +It was common to rewrite the `basePath` with the `pathRewrite` option: + +```js +// before +app.use('/user', proxy({ + target: 'http://www.example.org' + pathRewrite: { '^/user': '/secret' } +})); + +// after +app.use('/user', proxy({ target: 'http://www.example.org/secret' })); +``` + +When proxy is mounted at the root, `pathRewrite` should still work as in v2. + +```js +// not affected +app.use(proxy({ + target: 'http://www.example.org' + pathRewrite: { '^/user': '/secret' } +})); +``` + +### Removed "shorthand" usage + +Specify the `target` option. + +```js +// before +createProxyMiddleware('http:/www.example.org'); + +// after +createProxyMiddleware({ target: 'http:/www.example.org' }); +``` + +### Removed `context` argument + +The `context` argument has been moved to option: `pathFilter`. + +Functionality did not change. + +See [recipes/pathFilter.md](./recipes/pathFilter.md) for more information. + +```js +// before +createProxyMiddleware('/path', { target: 'http://www.example.org' }); + +// after +createProxyMiddleware({ + target: 'http://www.example.org', + pathFilter: '/path', +}); +``` + +### Removed `logProvider` and `logLevel` options + +Use your external logging library to _log_ and control the logging _level_. + +Only `info`, `warn`, `error` are used internally for compatibility across different loggers. + +If you use `winston`, make sure to enable interpolation: + +See [recipes/logger.md](./recipes/logger.md) for more information. + +```js +// new +createProxyMiddleware({ + target: 'http://www.example.org', + logger: console, +}); +``` + +### Refactored proxy events + +See [recipes/proxy-events.md](./recipes/proxy-events.md) for more information. + +```js +// before +createProxyMiddleware({ + target: 'http://www.example.org', + onError: () => {}, + onProxyReq: () => {}, + onProxyRes: () => {}, + onProxyReqWs: () => {}, + onOpen: () => {}, + onClose: () => {}, +}); + +// after +createProxyMiddleware({ + target: 'http://www.example.org', + on: { + error: () => {}, + proxyReq: () => {}, + proxyRes: () => {}, + proxyReqWs: () => {}, + open: () => {}, + close: () => {}, + }, +}); +``` diff --git a/README.md b/README.md index 6201fc82..ed988252 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,21 @@ Powered by the popular Nodejitsu [`http-proxy`](https://github.com/nodejitsu/nod ## ⚠️ Note -This page is showing documentation for version v2.x.x ([release notes](https://github.com/chimurai/http-proxy-middleware/releases)) +This page is showing documentation for version v3.x.x ([release notes](https://github.com/chimurai/http-proxy-middleware/releases)) -If you're looking for v0.x documentation. Go to: -https://github.com/chimurai/http-proxy-middleware/tree/v0.21.0#readme +See [MIGRATION.md](https://github.com/chimurai/http-proxy-middleware/blob/master/MIGRATION.md) for details on how to migrate from v2.x.x to v3.x.x + +If you're looking for older documentation. Go to: + +- +- ## TL;DR Proxy `/api` requests to `http://www.example.org` +:bulb: **Tip:** Set the option `changeOrigin` to `true` for [name-based virtual hosted sites](http://en.wikipedia.org/wiki/Virtual_hosting#Name-based). + ```javascript // javascript @@ -30,8 +36,12 @@ const app = express(); app.use( '/api', - createProxyMiddleware({ target: 'http://www.example.org/secret', changeOrigin: true }) + createProxyMiddleware({ + target: 'http://www.example.org/secret', + changeOrigin: true, + }) ); + app.listen(3000); // proxy and change the base path from "/api" to "/secret" @@ -48,8 +58,12 @@ const app = express(); app.use( '/api', - createProxyMiddleware({ target: 'http://www.example.org/api', changeOrigin: true }) + createProxyMiddleware({ + target: 'http://www.example.org/api', + changeOrigin: true, + }) ); + app.listen(3000); // proxy and keep the same base path "/api" @@ -58,8 +72,6 @@ app.listen(3000); _All_ `http-proxy` [options](https://github.com/nodejitsu/node-http-proxy#options) can be used, along with some extra `http-proxy-middleware` [options](#options). -:bulb: **Tip:** Set the option `changeOrigin` to `true` for [name-based virtual hosted sites](http://en.wikipedia.org/wiki/Virtual_hosting#Name-based). - ## Table of Contents @@ -160,9 +172,9 @@ app.use( `app.use` documentation: -- express: http://expressjs.com/en/4x/api.html#app.use -- connect: https://github.com/senchalabs/connect#mount-middleware -- polka: https://github.com/lukeed/polka#usebase-fn +- express: +- connect: +- polka: ## Options @@ -302,7 +314,7 @@ const { loggerPlugin, // log proxy events to a logger (ie. console) errorResponsePlugin, // return 5xx response on proxy error proxyEventsPlugin, // implements the "on:" option -} = require('http-proxy-middleware/plugins/default'); +} = require('http-proxy-middleware'); createProxyMiddleware({ target: `http://example.org`, @@ -316,6 +328,10 @@ createProxyMiddleware({ Configure a logger to output information from http-proxy-middleware: ie. `console`, `winston`, `pino`, `bunyan`, `log4js`, etc... +Only `info`, `warn`, `error` are used internally for compatibility across different loggers. + +If you use `winston`, make sure to enable interpolation: + See also logger recipes ([recipes/logger.md](https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/logger.md)) for more details. ```javascript @@ -424,10 +440,12 @@ The following options are provided by the underlying [http-proxy](https://github - **option.autoRewrite**: rewrites the location host/port on (301/302/307/308) redirects based on requested host/port. Default: false. - **option.protocolRewrite**: rewrites the location protocol on (301/302/307/308) redirects to 'http' or 'https'. Default: null. - **option.cookieDomainRewrite**: rewrites domain of `set-cookie` headers. Possible values: + - `false` (default): disable cookie rewriting - String: new domain, for example `cookieDomainRewrite: "new.domain"`. To remove the domain, use `cookieDomainRewrite: ""`. - Object: mapping of domains to new domains, use `"*"` to match all domains. For example keep one domain unchanged, rewrite one domain and remove other domains: + ```json cookieDomainRewrite: { "unchanged.domain": "unchanged.domain", @@ -435,11 +453,14 @@ The following options are provided by the underlying [http-proxy](https://github "*": "" } ``` + - **option.cookiePathRewrite**: rewrites path of `set-cookie` headers. Possible values: + - `false` (default): disable cookie rewriting - String: new path, for example `cookiePathRewrite: "/newPath/"`. To remove the path, use `cookiePathRewrite: ""`. To set path to root use `cookiePathRewrite: "/"`. - Object: mapping of paths to new paths, use `"*"` to match all paths. For example, to keep one path unchanged, rewrite one path and remove other paths: + ```json cookiePathRewrite: { "/unchanged.path/": "/unchanged.path/", @@ -447,6 +468,7 @@ The following options are provided by the underlying [http-proxy](https://github "*": "" } ``` + - **option.headers**: object, adds [request headers](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields). (Example: `{host:'www.example.org'}`) - **option.proxyTimeout**: timeout (in millis) when proxy receives no response from target - **option.timeout**: timeout (in millis) for incoming requests diff --git a/cspell.json b/cspell.json index 477545c3..34d03fcc 100644 --- a/cspell.json +++ b/cspell.json @@ -2,14 +2,21 @@ "language": "en", "spellCheckDelayMs": 500, "dictionaries": ["node", "npm", "typescript", "contributors"], - "ignorePaths": ["node_modules/**", "coverage/**", "dist/**", "package.json", "yarn.lock"], + "ignorePaths": [ + "node_modules/**", + "coverage/**", + "dist/**", + "package.json", + "yarn.lock", + "*.tgz" + ], "dictionaryDefinitions": [ { "name": "contributors", "path": "CONTRIBUTORS.txt" } ], - "ignoreRegExpList": ["[a-z]+path"], + "ignoreRegExpList": ["[a-z]+path", "\\]\\(#[a-z-]+\\)"], "words": [ "camelcase", "codesandbox", diff --git a/examples/connect/index.js b/examples/connect/index.js index 930097f3..d04379ee 100644 --- a/examples/connect/index.js +++ b/examples/connect/index.js @@ -11,7 +11,6 @@ const { createProxyMiddleware } = require('../../dist'); // require('http-proxy- const jsonPlaceholderProxy = createProxyMiddleware({ target: 'http://jsonplaceholder.typicode.com/users', changeOrigin: true, // for vhosted sites, changes host header to match to target's host - logLevel: 'debug', }); const app = connect(); diff --git a/examples/express/index.js b/examples/express/index.js index bb266f65..f9d7025f 100644 --- a/examples/express/index.js +++ b/examples/express/index.js @@ -10,7 +10,6 @@ const { createProxyMiddleware } = require('../../dist'); // require('http-proxy- const jsonPlaceholderProxy = createProxyMiddleware({ target: 'http://jsonplaceholder.typicode.com/users', changeOrigin: true, // for vhosted sites, changes host header to match to target's host - logLevel: 'debug', logger: console, }); diff --git a/recipes/proxy-events.md b/recipes/proxy-events.md index d3594e2b..929aca17 100644 --- a/recipes/proxy-events.md +++ b/recipes/proxy-events.md @@ -1,6 +1,6 @@ # Proxy Events -Subscribe to [`http-proxy`](https://github.com/nodejitsu/node-http-proxy) [![GitHub stars](https://img.shields.io/github/stars/nodejitsu/node-http-proxy.svg?style=social&label=Star)](https://github.com/nodejitsu/node-http-proxy) events: `error`, `proxyReq`, `proxyReqWs`, `proxyRes`, `open`, `close`, `start`, `end`, `econnreset`. +Subscribe to `http-proxy` events: `error`, `proxyReq`, `proxyReqWs`, `proxyRes`, `open`, `close`, `start`, `end`, `econnreset`. ## on.error diff --git a/recipes/websocket.md b/recipes/websocket.md index 70ae3327..eb033eb1 100644 --- a/recipes/websocket.md +++ b/recipes/websocket.md @@ -5,8 +5,9 @@ This example will create a proxy middleware with websocket support. ```javascript const { createProxyMiddleware } = require('http-proxy-middleware'); -const socketProxy = createProxyMiddleware('/socket', { +const socketProxy = createProxyMiddleware({ target: 'http://localhost:3000', + pathFilter: '/socket', ws: true, }); ``` @@ -21,12 +22,13 @@ const { createProxyMiddleware } = require('http-proxy-middleware'); const options = { target: 'http://localhost:3000', ws: true, + pathFilter: '/socket', pathRewrite: { '^/socket': '', }, }; -const socketProxy = createProxyMiddleware('/socket', options); +const socketProxy = createProxyMiddleware(options); ``` ## WebSocket - Server update subscription @@ -38,8 +40,9 @@ Subscribe to server's upgrade event. ```javascript const { createProxyMiddleware } = require('http-proxy-middleware'); -const socketProxy = createProxyMiddleware('/socket', { +const socketProxy = createProxyMiddleware({ target: 'http://localhost:3000', + pathFilter: '/socket', ws: true, }); diff --git a/src/get-plugins.ts b/src/get-plugins.ts index 4eeffa82..b70fdaf3 100644 --- a/src/get-plugins.ts +++ b/src/get-plugins.ts @@ -7,9 +7,12 @@ import { } from './plugins/default'; export function getPlugins(options: Options): Plugin[] { + // don't load default errorResponsePlugin if user has specified their own + const maybeErrorResponsePlugin = !!options.on?.error ? [] : [errorResponsePlugin]; + const defaultPlugins: Plugin[] = !!options.ejectPlugins ? [] // no default plugins when ejecting - : [debugProxyErrorsPlugin, proxyEventsPlugin, loggerPlugin, errorResponsePlugin]; + : [debugProxyErrorsPlugin, proxyEventsPlugin, loggerPlugin, ...maybeErrorResponsePlugin]; const userPlugins: Plugin[] = options.plugins ?? []; return [...defaultPlugins, ...userPlugins]; } diff --git a/src/http-proxy-middleware.ts b/src/http-proxy-middleware.ts index 7dad08a4..83f81907 100644 --- a/src/http-proxy-middleware.ts +++ b/src/http-proxy-middleware.ts @@ -116,6 +116,15 @@ export class HttpProxyMiddleware { * @return {Object} proxy options */ private prepareProxyRequest = async (req: Request) => { + /** + * Incorrect usage confirmed: https://github.com/expressjs/express/issues/4854#issuecomment-1066171160 + * Temporary restore req.url patch for {@link src/legacy/create-proxy-middleware.ts legacyCreateProxyMiddleware()} + * FIXME: remove this patch in future release + */ + if ((this.middleware as unknown as any).__LEGACY_HTTP_PROXY_MIDDLEWARE__) { + req.url = (req as unknown as any).originalUrl || req.url; + } + const newProxyOptions = Object.assign({}, this.proxyOptions); // Apply in order: diff --git a/src/index.ts b/src/index.ts index 46ce014f..130f9c6b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,13 +6,16 @@ export function createProxyMiddleware(options: Options): RequestHandler { return middleware; } -/** - * @deprecated - */ -// export function legacyCreateProxyMiddleware(pathFilter: Filter, options: Options) { -// return createProxyMiddleware({ ...options, pathFilter }); -// } - export * from './handlers'; export type { Filter, Options, RequestHandler } from './types'; + +/** + * Default plugins + */ +export * from './plugins/default'; + +/** + * Legacy exports + */ +export * from './legacy'; diff --git a/src/legacy/create-proxy-middleware.ts b/src/legacy/create-proxy-middleware.ts new file mode 100644 index 00000000..3a659131 --- /dev/null +++ b/src/legacy/create-proxy-middleware.ts @@ -0,0 +1,33 @@ +import { createProxyMiddleware } from '..'; +import { Debug } from '../debug'; +import { Filter, RequestHandler } from '../types'; +import { legacyOptionsAdapter } from './options-adapter'; +import { LegacyOptions } from './types'; + +const debug = Debug.extend('legacy-create-proxy-middleware'); + +/** + * @deprecated + * This function is deprecated and will be removed in a future version. + * + * Use {@link createProxyMiddleware} instead. + */ +export function legacyCreateProxyMiddleware(shortHand: string): RequestHandler; +export function legacyCreateProxyMiddleware(legacyOptions: LegacyOptions): RequestHandler; +export function legacyCreateProxyMiddleware( + legacyContext: Filter, + legacyOptions: LegacyOptions +): RequestHandler; +export function legacyCreateProxyMiddleware(legacyContext, legacyOptions?): RequestHandler { + debug('init'); + + const options = legacyOptionsAdapter(legacyContext, legacyOptions); + + const proxyMiddleware = createProxyMiddleware(options); + + // https://github.com/chimurai/http-proxy-middleware/pull/731/files#diff-07e6ad10bda0df091b737caed42767657cd0bd74a01246a1a0b7ab59c0f6e977L118 + debug('add marker for patching req.url (old behavior)'); + (proxyMiddleware as any).__LEGACY_HTTP_PROXY_MIDDLEWARE__ = true; + + return proxyMiddleware; +} diff --git a/src/legacy/index.ts b/src/legacy/index.ts new file mode 100644 index 00000000..b7e8b718 --- /dev/null +++ b/src/legacy/index.ts @@ -0,0 +1 @@ +export * from './public'; diff --git a/src/legacy/options-adapter.ts b/src/legacy/options-adapter.ts new file mode 100644 index 00000000..305b9eb7 --- /dev/null +++ b/src/legacy/options-adapter.ts @@ -0,0 +1,67 @@ +import * as url from 'url'; +import { Filter, Options } from '..'; +import { LegacyOptions } from './types'; +import { Debug } from '../debug'; + +const debug = Debug.extend('legacy-options-adapter'); + +// https://github.com/chimurai/http-proxy-middleware/blob/7341704d0aa9d1606dfd37ebfdffddd34c894784/src/_handlers.ts#L20-L27 +const proxyEventMap = { + onError: 'error', + onProxyReq: 'proxyReq', + onProxyRes: 'proxyRes', + onProxyReqWs: 'proxyReqWs', + onOpen: 'open', + onClose: 'close', +}; + +/** + * Convert {@link LegacyOptions legacy Options} to new {@link Options} + */ +export function legacyOptionsAdapter( + legacyContext: Filter | LegacyOptions, + legacyOptions: LegacyOptions +): Options { + let options: LegacyOptions; + + // https://github.com/chimurai/http-proxy-middleware/pull/716 + if (typeof legacyContext === 'string' && !!url.parse(legacyContext).host) { + throw new Error( + `Shorthand syntax is removed from legacyCreateProxyMiddleware(). + Please use "legacyCreateProxyMiddleware({ target: 'http://www.example.org' })" instead.` + ); + } + + // detect old "context" argument and convert to "options.pathFilter" + // https://github.com/chimurai/http-proxy-middleware/pull/722/files#diff-a2a171449d862fe29692ce031981047d7ab755ae7f84c707aef80701b3ea0c80L4 + if (legacyContext && legacyOptions) { + debug('map legacy context/filter to options.pathFilter'); + options = { ...legacyOptions, pathFilter: legacyContext as Filter }; + } else if (legacyContext && !legacyOptions) { + options = { ...(legacyContext as Options) }; + } + + // map old event names to new event names + // https://github.com/chimurai/http-proxy-middleware/pull/745/files#diff-c54113cf61ec99691748a3890bfbeb00e10efb3f0a76f03a0fd9ec49072e410aL48-L53 + Object.entries(proxyEventMap).forEach(([legacyEventName, proxyEventName]) => { + if (options[legacyEventName]) { + options.on = { ...options.on }; + options.on[proxyEventName] = options[legacyEventName]; + debug('map legacy event "%s" to "on.%s"', legacyEventName, proxyEventName); + } + }); + + // map old logProvider to new logger + // https://github.com/chimurai/http-proxy-middleware/pull/749 + const logProvider = options.logProvider && options.logProvider(); + const logLevel = options.logLevel; + debug('legacy logLevel', logLevel); + debug('legacy logProvider: %O', logProvider); + + if (typeof logLevel === 'string' && logLevel !== 'silent') { + options.logger = logProvider; + debug('map "logProvider" to "logger"'); + } + + return options; +} diff --git a/src/legacy/public.ts b/src/legacy/public.ts new file mode 100644 index 00000000..72b687ce --- /dev/null +++ b/src/legacy/public.ts @@ -0,0 +1,2 @@ +export { legacyCreateProxyMiddleware } from './create-proxy-middleware'; +export { LegacyOptions } from './types'; diff --git a/src/legacy/types.ts b/src/legacy/types.ts new file mode 100644 index 00000000..10137424 --- /dev/null +++ b/src/legacy/types.ts @@ -0,0 +1,112 @@ +// import * as httpProxy from 'http-proxy'; +import { Options } from '..'; + +/** + * @deprecated + * + * Will be removed in a future version. + */ +export interface LegacyOptions extends Options { + /** + * @deprecated + * Use `on.error` instead. + * + * @example + * ```js + * { + * on: { + * error: () => {} + * } + * ``` + */ + onError?: (...args: any[]) => void; //httpProxy.ErrorCallback; + /** + * @deprecated + * Use `on.proxyRes` instead. + * + * @example + * ```js + * { + * on: { + * proxyRes: () => {} + * } + * ``` + */ + onProxyRes?: (...args: any[]) => void; //httpProxy.ProxyResCallback; + /** + * @deprecated + * Use `on.proxyReq` instead. + * + * @example + * ```js + * { + * on: { + * proxyReq: () => {} + * } + * ``` + */ + onProxyReq?: (...args: any[]) => void; //httpProxy.ProxyReqCallback; + /** + * @deprecated + * Use `on.proxyReqWs` instead. + * + * @example + * ```js + * { + * on: { + * proxyReqWs: () => {} + * } + * ``` + */ + onProxyReqWs?: (...args: any[]) => void; //httpProxy.ProxyReqWsCallback; + /** + * @deprecated + * Use `on.open` instead. + * + * @example + * ```js + * { + * on: { + * open: () => {} + * } + * ``` + */ + onOpen?: (...args: any[]) => void; //httpProxy.OpenCallback; + /** + * @deprecated + * Use `on.close` instead. + * + * @example + * ```js + * { + * on: { + * close: () => {} + * } + * ``` + */ + onClose?: (...args: any[]) => void; //httpProxy.CloseCallback; + /** + * @deprecated + * Use `logger` instead. + * + * @example + * ```js + * { + * logger: console + * } + * ``` + */ + logProvider?: any; + /** + * @deprecated + * Use `logger` instead. + * + * @example + * ```js + * { + * logger: console + * } + * ``` + */ + logLevel?: any; +} diff --git a/test/e2e/http-proxy-middleware.spec.ts b/test/e2e/http-proxy-middleware.spec.ts index 3232fd0a..795b29af 100644 --- a/test/e2e/http-proxy-middleware.spec.ts +++ b/test/e2e/http-proxy-middleware.spec.ts @@ -286,6 +286,36 @@ describe('E2E http-proxy-middleware', () => { }); }); + describe('option.on.error - custom error handler', () => { + beforeEach(() => { + agent = request( + createApp( + createProxyMiddleware({ + target: `http://localhost:666`, // unreachable host on port:666 + on: { + error(err, req, res) { + if (err) { + (res as express.Response).writeHead(418); // different error code + res.end("I'm a teapot"); // no response body + } + }, + }, + }) + ) + ); + }); + + it('should respond with custom http status code', async () => { + const response = await agent.get(`/api/some/endpoint`).expect(418); + expect(response.status).toBe(418); + }); + + it('should respond with custom status message', async () => { + const response = await agent.get(`/api/some/endpoint`).expect(418); + expect(response.text).toBe("I'm a teapot"); + }); + }); + describe('option.onProxyRes', () => { beforeEach(() => { agent = request( diff --git a/test/legacy/http-proxy-middleware.spec.ts b/test/legacy/http-proxy-middleware.spec.ts new file mode 100644 index 00000000..d46ee7a9 --- /dev/null +++ b/test/legacy/http-proxy-middleware.spec.ts @@ -0,0 +1,104 @@ +import { createApp, createAppWithPath } from '../e2e/test-kit'; +import { legacyCreateProxyMiddleware, LegacyOptions } from '../../src'; +import * as request from 'supertest'; +import { Mockttp, getLocal } from 'mockttp'; + +describe('legacyCreateProxyMiddleware()', () => { + const mockServer: Mockttp = getLocal(); + + beforeEach(() => mockServer.start()); + afterEach(() => mockServer.stop()); + + it('should throw when short hand is used', () => { + expect(() => legacyCreateProxyMiddleware(`http://localhost:${mockServer.port}`)) + .toThrowErrorMatchingInlineSnapshot(` + "Shorthand syntax is removed from legacyCreateProxyMiddleware(). + Please use \\"legacyCreateProxyMiddleware({ target: 'http://www.example.org' })\\" instead." + `); + }); + + it('should expose external websocket upgrade handler', () => { + const proxyMiddleware = legacyCreateProxyMiddleware({ + target: `http://localhost:${mockServer.port}`, + }); + expect(proxyMiddleware.upgrade).toBeDefined(); + }); + + it('should proxy to /users', async () => { + mockServer.forGet('/users').thenReply(200, 'legacy OK'); + + const proxyMiddleware = legacyCreateProxyMiddleware({ + target: `http://localhost:${mockServer.port}`, + }); + + const app = createApp(proxyMiddleware); + const response = await request(app).get('/users').expect(200); + + expect(response.text).toBe('legacy OK'); + }); + + it('should proxy with old context option', async () => { + mockServer.forGet('/admin/users').thenReply(200, 'legacy OK'); + + const proxyMiddleware = legacyCreateProxyMiddleware('/admin', { + target: `http://localhost:${mockServer.port}`, + }); + + const app = createApp(proxyMiddleware); + const response = await request(app).get('/admin/users').expect(200); + + expect(response.text).toBe('legacy OK'); + }); + + it('should proxy to /users with legacy patched req.url behavior', async () => { + mockServer.forGet('/users').thenReply(200, 'legacy OK'); + + const proxyMiddleware = legacyCreateProxyMiddleware({ + target: `http://localhost:${mockServer.port}`, + }); + + const app = createAppWithPath('/users', proxyMiddleware); + const response = await request(app).get('/users').expect(200); + + expect(response.text).toBe('legacy OK'); + }); + + it('should fail to proxy to /users with legacy patched req.url behavior', async () => { + mockServer.forGet('/users').thenReply(200, 'legacy OK'); + + const proxyMiddleware = legacyCreateProxyMiddleware({ + target: `http://localhost:${mockServer.port}/users`, + }); + + const app = createAppWithPath('/users', proxyMiddleware); + await request(app).get('/users').expect(503); + }); + + it('should respond with legacy onError handler and log error', async () => { + mockServer.forGet('/users').thenCloseConnection(); + + const mockLogger = { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + const legacyOptions: LegacyOptions = { + target: `http://localhost:${mockServer.port}`, + logLevel: 'error', + logProvider: () => mockLogger, + onError(err, req, res) { + res.status(500).send('my legacy error'); + }, + }; + + const proxyMiddleware = legacyCreateProxyMiddleware(legacyOptions); + + const app = createAppWithPath('/users', proxyMiddleware); + const response = await request(app).get('/users').expect(500); + + expect(response.text).toBe('my legacy error'); + + expect(mockLogger.error).toHaveBeenCalledTimes(1); + }); +}); diff --git a/test/types.spec.ts b/test/types.spec.ts index 0e644a5d..b0eaf6c5 100644 --- a/test/types.spec.ts +++ b/test/types.spec.ts @@ -124,4 +124,23 @@ describe('http-proxy-middleware TypeScript Types', () => { }); }); }); + + describe('express request and response types', () => { + it('should get TypeScript errors when express specific properties are used', () => { + options = { + on: { + proxyReq(proxyReq, req, res, options) { + // @ts-expect-error explanation: should error when express properties are used + req.params; + }, + proxyRes(proxyRes, req, res) { + // @ts-expect-error explanation: should error when express properties are used + res.status(200).send('OK'); + }, + }, + }; + + expect(options).toBeDefined(); + }); + }); }); diff --git a/test/unit/get-plugins.spec.ts b/test/unit/get-plugins.spec.ts index 2292c297..21193a33 100644 --- a/test/unit/get-plugins.spec.ts +++ b/test/unit/get-plugins.spec.ts @@ -5,7 +5,7 @@ import { loggerPlugin, errorResponsePlugin, proxyEventsPlugin, -} from '../../src/plugins/default'; +} from '../../src'; describe('getPlugins', () => { let plugins: Plugin[]; @@ -85,4 +85,24 @@ describe('getPlugins', () => { ] `); }); + + it('should not configure errorResponsePlugin when user specifies their own error handler', () => { + const myErrorHandler = () => { + /* noop */ + }; + plugins = getPlugins({ + on: { + error: myErrorHandler, + }, + }); + + expect(plugins).toHaveLength(3); + expect(plugins.map((plugin) => plugin.name)).toMatchInlineSnapshot(` + Array [ + "debugProxyErrorsPlugin", + "proxyEventsPlugin", + "loggerPlugin", + ] + `); + }); });