diff --git a/.changeset/shaggy-lies-know.md b/.changeset/shaggy-lies-know.md new file mode 100644 index 0000000000..cc016ed226 --- /dev/null +++ b/.changeset/shaggy-lies-know.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-proxy-backend': minor +--- + +The proxy-backend plugin now supports reviving request bodies that have previously been consumed by an express middleware (e.g. `express.json()`). This is done by setting `reviveRequestBody: true` on the proxy configuration. In order to preserve the current behavior, the proxy will **not** revive request bodies by default. + +The following is an example of a proxy configuration that revives request bodies: + +```diff +proxy: + '/target': + target: 'http://proxy-route' ++ reviveRequestBody: true +``` diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts index 59fc0dc5a6..d248845df1 100644 --- a/plugins/proxy-backend/config.d.ts +++ b/plugins/proxy-backend/config.d.ts @@ -50,6 +50,11 @@ export interface Config { * and headers that are set by the proxy will be forwarded. */ allowedHeaders?: string[]; + /** + * Revive request body if it was consumed by a middleware before reaching the proxy. By default, the request + * body is not revived. + */ + reviveRequestBody?: boolean; }; }; } diff --git a/plugins/proxy-backend/src/service/router.test.ts b/plugins/proxy-backend/src/service/router.test.ts index afe78aebeb..aa51fe5a08 100644 --- a/plugins/proxy-backend/src/service/router.test.ts +++ b/plugins/proxy-backend/src/service/router.test.ts @@ -18,11 +18,16 @@ import { getVoidLogger, SingleHostDiscovery } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { Request, Response } from 'express'; import * as http from 'http'; -import { createProxyMiddleware, Options } from 'http-proxy-middleware'; +import { + createProxyMiddleware, + fixRequestBody, + Options, +} from 'http-proxy-middleware'; import { buildMiddleware, createRouter } from './router'; jest.mock('http-proxy-middleware', () => ({ createProxyMiddleware: jest.fn(() => () => undefined), + fixRequestBody: jest.fn(), })); const mockCreateProxyMiddleware = createProxyMiddleware as jest.MockedFunction< @@ -389,6 +394,41 @@ describe('buildMiddleware', () => { expect(Object.keys(testClientResponse.headers!)).toEqual(['set-cookie']); }); + it('revives request body when configured', async () => { + buildMiddleware('/proxy', logger, '/test', { + target: 'http://mocked', + reviveRequestBody: true, + }); + + expect(createProxyMiddleware).toHaveBeenCalledTimes(1); + + const config = mockCreateProxyMiddleware.mock.calls[0][1] as Options; + + expect(config).toBeDefined(); + expect(config.onProxyReq).toBeDefined(); + + config.onProxyReq!( + {} as http.ClientRequest, + {} as Request, + {} as Response, + {}, + ); + expect(fixRequestBody).toHaveBeenCalledTimes(1); + }); + + it('does not revive request body when not configured', async () => { + buildMiddleware('/proxy', logger, '/test', { + target: 'http://mocked', + }); + + expect(createProxyMiddleware).toHaveBeenCalledTimes(1); + + const config = mockCreateProxyMiddleware.mock.calls[0][1] as Options; + + expect(config).toBeDefined(); + expect(config.onProxyReq).not.toBeDefined(); + }); + it('rejects malformed target URLs', async () => { expect(() => buildMiddleware('/proxy', logger, '/test', 'backstage.io'), diff --git a/plugins/proxy-backend/src/service/router.ts b/plugins/proxy-backend/src/service/router.ts index 224c2c6202..1f66bc7f6b 100644 --- a/plugins/proxy-backend/src/service/router.ts +++ b/plugins/proxy-backend/src/service/router.ts @@ -19,6 +19,7 @@ import express from 'express'; import Router from 'express-promise-router'; import { createProxyMiddleware, + fixRequestBody, Options, RequestHandler, } from 'http-proxy-middleware'; @@ -58,6 +59,7 @@ export interface RouterOptions { export interface ProxyConfig extends Options { allowedMethods?: string[]; allowedHeaders?: string[]; + reviveRequestBody?: boolean; } // Creates a proxy middleware, possibly with defaults added on top of the @@ -175,6 +177,10 @@ export function buildMiddleware( }); }; + if (fullConfig.reviveRequestBody) { + fullConfig.onProxyReq = fixRequestBody; + } + return createProxyMiddleware(filter, fullConfig); }