Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## next

- feat(definePlugin): helper function to create plugins

## [v4.0.0](https://github.com/chimurai/http-proxy-middleware/releases/tag/v4.0.0)

- fix(types): fix Logger type
Expand Down
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ _All_ `httpxy` [options](https://github.com/unjs/httpxy#options) can be used, al
- [`router` (object/function)](#router-objectfunction)
- [`plugins` (Array)](#plugins-array)
- [`ejectPlugins` (boolean) default: `false`](#ejectplugins-boolean-default-false)
- [`definePlugin` helper](#defineplugin-helper)
- [`logger` (Object)](#logger-object)
- [`httpxy` events](#httpxy-events)
- [`httpxy` options](#httpxy-options)
Expand Down Expand Up @@ -281,12 +282,9 @@ NOTE: register your own error handlers to prevent server from crashing.
```js
// eject default plugins and manually add them back
import {
debugProxyErrorsPlugin,
// log proxy events to a logger (ie. console)
errorResponsePlugin,
// subscribe to proxy errors to prevent server from crashing
loggerPlugin,
// return 5xx response on proxy error
debugProxyErrorsPlugin, // subscribe to proxy errors to prevent server from crashing
errorResponsePlugin, // return 5xx response on proxy error
loggerPlugin, // log proxy events to a logger (ie. console)
proxyEventsPlugin, // implements the "on:" option
} from 'http-proxy-middleware';

Expand All @@ -298,6 +296,26 @@ createProxyMiddleware({
});
```

## `definePlugin` helper

Create your own `http-proxy-middleware` plugin.

(Default plugins are created with `definePlugin`)

```ts
import { createProxyMiddleware, definePlugin } from 'http-proxy-middleware';

const myPlugin = definePlugin((proxyServer, options) => {
// plugin implementation
});

// use configure and use plugin
createProxyMiddleware({
target: `http://example.org`,
plugins: [myPlugin],
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```

### `logger` (Object)

Configure a logger to output information from http-proxy-middleware: ie. `console`, `winston`, `pino`, `bunyan`, `log4js`, etc...
Expand Down
5 changes: 1 addition & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,4 @@ export * from './handlers/index.js';

export type { Plugin, Filter, Options, RequestHandler } from './types.js';

/**
* Default plugins
*/
export * from './plugins/default/index.js';
export * from './plugins/index.js';
5 changes: 3 additions & 2 deletions src/plugins/default/debug-proxy-errors-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Debug } from '../../debug.js';
import type { Plugin } from '../../types.js';
import { definePlugin } from '../define-plugin.js';

const debug = Debug.extend('debug-proxy-errors-plugin');

/**
* Subscribe to {@link https://github.com/unjs/httpxy#events `httpxy` error events} to prevent server from crashing.
* Errors are logged with {@link https://www.npmjs.com/package/debug debug} library.
*/
export const debugProxyErrorsPlugin: Plugin = (proxyServer): void => {
export const debugProxyErrorsPlugin: Plugin = definePlugin((proxyServer, options) => {
/**
* The old `http-proxy` doesn't handle any errors by default (https://github.com/http-party/node-http-proxy#listening-for-proxy-events)
* > We do not do any error handling of messages passed between client and proxy, and messages passed between proxy and target, so it is recommended that you listen on errors and handle them.
Expand Down Expand Up @@ -64,4 +65,4 @@ export const debugProxyErrorsPlugin: Plugin = (proxyServer): void => {
proxyServer.on('econnreset', (error, req, res, target) => {
debug(`httpxy econnreset event: \n%O`, error);
});
};
});
5 changes: 3 additions & 2 deletions src/plugins/default/error-response-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Socket } from 'node:net';
import { getStatusCode } from '../../status-code.js';
import type { Plugin } from '../../types.js';
import { sanitize } from '../../utils/sanitize.js';
import { definePlugin } from '../define-plugin.js';

function isResponseLike(obj: any): obj is http.ServerResponse {
return obj && typeof obj.writeHead === 'function';
Expand All @@ -13,7 +14,7 @@ function isSocketLike(obj: any): obj is Socket {
return obj && typeof obj.write === 'function' && !('writeHead' in obj);
}

export const errorResponsePlugin: Plugin = (proxyServer, options) => {
export const errorResponsePlugin: Plugin = definePlugin((proxyServer, options) => {
proxyServer.on('error', (err, req, res, target?) => {
// Re-throw error. Not recoverable since req & res are empty.
if (!req || !res) {
Expand All @@ -32,4 +33,4 @@ export const errorResponsePlugin: Plugin = (proxyServer, options) => {
res.destroy();
}
});
};
});
7 changes: 4 additions & 3 deletions src/plugins/default/logger-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getLogger } from '../../logger.js';
import type { Plugin } from '../../types.js';
import { createUrl } from '../../utils/create-url.js';
import { getPort } from '../../utils/logger-plugin.js';
import { definePlugin } from '../define-plugin.js';

type ExpressRequest = {
/** Express req.baseUrl */
Expand All @@ -19,7 +20,7 @@ type BrowserSyncRequest = {
/** Request Types from different server libs */
type FrameworkRequest = IncomingMessage & ExpressRequest & BrowserSyncRequest;

export const loggerPlugin: Plugin = (proxyServer, options) => {
export const loggerPlugin: Plugin = definePlugin<FrameworkRequest>((proxyServer, options) => {
const logger = getLogger(options);

proxyServer.on('error', (err, req, res, target?) => {
Expand All @@ -40,7 +41,7 @@ export const loggerPlugin: Plugin = (proxyServer, options) => {
* [HPM] GET /users/ -> http://jsonplaceholder.typicode.com/users/ [304]
* ```
*/
proxyServer.on('proxyRes', (proxyRes: any, req: FrameworkRequest, res) => {
proxyServer.on('proxyRes', (proxyRes: any, req, res) => {
// BrowserSync uses req.originalUrl
// Next.js doesn't have req.baseUrl
const originalUrl = req.originalUrl ?? `${req.baseUrl || ''}${req.url}`;
Expand Down Expand Up @@ -80,4 +81,4 @@ export const loggerPlugin: Plugin = (proxyServer, options) => {
proxyServer.on('close', (req, proxySocket, proxyHead) => {
logger.info('[HPM] Client disconnected: %o', proxySocket.address());
});
};
});
5 changes: 3 additions & 2 deletions src/plugins/default/proxy-events.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Debug } from '../../debug.js';
import type { Plugin } from '../../types.js';
import { getFunctionName } from '../../utils/function.js';
import { definePlugin } from '../define-plugin.js';

const debug = Debug.extend('proxy-events-plugin');

Expand All @@ -24,7 +25,7 @@ const debug = Debug.extend('proxy-events-plugin');
* });
* ```
*/
export const proxyEventsPlugin: Plugin = (proxyServer, options) => {
export const proxyEventsPlugin: Plugin = definePlugin((proxyServer, options) => {
if (!options.on) {
return;
}
Expand All @@ -42,4 +43,4 @@ export const proxyEventsPlugin: Plugin = (proxyServer, options) => {
proxyServer.on<keyof typeof options.on>(eventName, handler as (...args: unknown[]) => void);
}
}
};
});
36 changes: 36 additions & 0 deletions src/plugins/define-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type * as http from 'node:http';

// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { ProxyServer } from 'httpxy';

// eslint-disable-next-line @typescript-eslint/no-unused-vars
import type { Plugin, Options } from '../types.js';

/**
* Helper function to define a http-proxy-middleware plugin
* @see proxyServer {@link ProxyServer} - proxy server instance to which the plugin is being applied
* @see options {@link Options} - options object passed to `createProxyMiddleware`
*
* @example defining a plugin
* ```js
* export const myPlugin = definePlugin((proxyServer, options) => {
* // plugin implementation
* });
* ```
*
* @example using a plugin
* ```js
* createProxyMiddleware({
* target: 'http://example.com',
* plugins: [myPlugin],
* });
* ```
*
* @since 4.1.0
*/
export function definePlugin<
TReq extends http.IncomingMessage = http.IncomingMessage,
TRes extends http.ServerResponse = http.ServerResponse,
>(fn: Plugin<TReq, TRes>): Plugin<TReq, TRes> {
return fn;
}
5 changes: 5 additions & 0 deletions src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// definePlugin()
export * from './define-plugin.js';

// default plugins
export * from './default/index.js';
7 changes: 4 additions & 3 deletions test/e2e/plugins.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { getLocal } from 'mockttp';
import request from 'supertest';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';

import type { Options, Plugin } from '../../src/types.js';
import { definePlugin } from '../../src/index.js';
import type { Options, Plugin } from '../../src/index.js';
import { createApp, createProxyMiddleware } from './test-kit.js';

describe('E2E Plugins', () => {
Expand All @@ -24,10 +25,10 @@ describe('E2E Plugins', () => {

mockTargetServer.forGet('/users/1').thenReply(200, '{"userName":"John"}');

const simplePlugin: Plugin = (proxy) => {
const simplePlugin: Plugin = definePlugin((proxy) => {
proxy.on('proxyReq', (proxyReq, req, res, options) => (proxyReqUrl = req.url));
proxy.on('proxyRes', (proxyRes, req, res) => (responseStatusCode = proxyRes.statusCode));
};
});

const config: Options = {
target: mockTargetServer.url,
Expand Down
43 changes: 43 additions & 0 deletions test/types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import express from 'express';
import { beforeEach, describe, expect, it } from 'vitest';

import {
definePlugin,
fixRequestBody,
createProxyMiddleware as middleware,
responseInterceptor,
Expand Down Expand Up @@ -442,4 +443,46 @@ describe('http-proxy-middleware TypeScript Types', () => {
expect(app).toBeDefined();
});
});

describe('definePlugin()', () => {
it('should define plugin with correct types', () => {
const myPlugin = definePlugin((proxyServer, options) => {
proxyServer.on('proxyReq', (proxyReq, req, res, options) => {
req.url;
res.statusCode;

// @ts-expect-error: should error when request is typed as `any`
req.unknownProperty;
// @ts-expect-error: should error when response is typed as `any`
res.unknownProperty;
});
});

expect(myPlugin).toBeDefined();
});

it('should define plugin with custom req & res types', () => {
interface MyRequest extends http.IncomingMessage {
myRequestParams: { [key: string]: string };
}

interface MyResponse extends http.ServerResponse {
myResponseParams: { [key: string]: string };
}

const myPlugin = definePlugin<MyRequest, MyResponse>((proxyServer, options) => {
proxyServer.on('proxyReq', (proxyReq, req, res, options) => {
req.myRequestParams;
res.myResponseParams;

// @ts-expect-error: should error when request is typed as `any`
req.unknownProperty;
// @ts-expect-error: should error when response is typed as `any`
res.unknownProperty;
});
});

expect(myPlugin).toBeDefined();
});
});
});
Loading
Loading