From 6d774764cd6e444cfe672af46478ca28781fd92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 26 Aug 2020 16:18:21 +0200 Subject: [PATCH] feat(proxy-backend): more proxy config tweaks and docs --- app-config.yaml | 12 ++--- packages/backend/src/index.ts | 2 +- packages/backend/src/plugins/proxy.ts | 11 +++-- plugins/proxy-backend/README.md | 46 ++++++++++++++++++- .../proxy-backend/src/service/router.test.ts | 1 + plugins/proxy-backend/src/service/router.ts | 42 +++++++++++++++-- .../src/service/standaloneServer.ts | 1 + 7 files changed, 95 insertions(+), 20 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 0a85198630..2efe6adc32 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -14,21 +14,15 @@ backend: client: sqlite3 connection: ':memory:' +# See README.md in the proxy-backend plugin for information on the configuration format proxy: - '/circleci/api': - target: 'https://circleci.com/api/v1.1' - changeOrigin: true - pathRewrite: - '^/proxy/circleci/api/': '/' + '/circleci/api': https://circleci.com/api/v1.1 '/jenkins/api': - target: 'http://localhost:8080' - changeOrigin: true + target: http://localhost:8080 headers: Authorization: $secret: env: JENKINS_BASIC_AUTH_HEADER - pathRewrite: - '^/proxy/jenkins/api/': '/' organization: name: Spotify diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 50f59795fc..91b1af06fb 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -82,7 +82,7 @@ async function main() { .addRouter('/auth', await auth(authEnv)) .addRouter('/identity', await identity(identityEnv)) .addRouter('/techdocs', await techdocs(techdocsEnv)) - .addRouter('/proxy', await proxy(proxyEnv)) + .addRouter('/proxy', await proxy(proxyEnv, '/proxy')) .addRouter('/graphql', await graphql(graphqlEnv)); await service.start().catch(err => { diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts index 4964de130e..e96acf69d3 100644 --- a/packages/backend/src/plugins/proxy.ts +++ b/packages/backend/src/plugins/proxy.ts @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + // @ts-ignore import { createRouter } from '@backstage/plugin-proxy-backend'; import { PluginEnvironment } from '../types'; -export default async function createPlugin({ - logger, - config, -}: PluginEnvironment) { - return await createRouter({ logger, config }); +export default async function createPlugin( + { logger, config }: PluginEnvironment, + pathPrefix: string, +) { + return await createRouter({ logger, config, pathPrefix }); } diff --git a/plugins/proxy-backend/README.md b/plugins/proxy-backend/README.md index d12031f495..baa2709820 100644 --- a/plugins/proxy-backend/README.md +++ b/plugins/proxy-backend/README.md @@ -19,8 +19,8 @@ const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); const service = createServiceBuilder(module) .loadConfig(configReader) - /** several different routers */ - .addRouter('/', await proxy(proxyEnv)); + /** ... other routers ... */ + .addRouter('/proxy', await proxy(proxyEnv, '/proxy')); ``` 2. Start the backend @@ -31,6 +31,48 @@ yarn workspace example-backend start This will launch the full example backend. +## Configuration + +Example: + +```yaml +# in app-config.yaml +proxy: + '/simple-example': http://simple.example.com:8080 + '/larger-example/v1': + target: http://larger.example.com:8080/svc.v1 + headers: + Authorization: + $secret: + env: EXAMPLE_AUTH_HEADER +``` + +Each key under the proxy configuration entry is a route to match, below the prefix that the proxy +plugin is mounted on. For example, if the backend mounts the proxy plugin as `/proxy`, +the above configuration will lead to the proxy acting on backend requests to +`/proxy/simple-example/...` and `/proxy/larger-example/v1/...`. + +The value inside each route is either a simple URL string, or an object on the format accepted by +[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware). + +If the value is a string, it is assumed to correspond to: + +```yaml +target: +changeOrigin: true +pathRewrite: + '^/': '/' +``` + +When the target is an object, it is given verbatim to `http-proxy-middleware` except with the +following caveats for convenience: + +- If `changeOrigin` is not specified, it is set to `true`. This is the most commonly useful value. +- If `pathRewrite` is not specified, it is set to a single rewrite that removes the entire route. In the + above example, a rewrite of `'^/proxy/larger-example/v1/': '/'` is added. That means that a request to + `/proxy/larger-example/v1/some/path` will be translated to a request to + `http://larger.example.com:8080/svc.v1/some/path`. + ## Links - [http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware) diff --git a/plugins/proxy-backend/src/service/router.test.ts b/plugins/proxy-backend/src/service/router.test.ts index 085647bc4e..c7d7fff1d4 100644 --- a/plugins/proxy-backend/src/service/router.test.ts +++ b/plugins/proxy-backend/src/service/router.test.ts @@ -26,6 +26,7 @@ describe('createRouter', () => { const router = await createRouter({ config, logger, + pathPrefix: '/proxy', }); expect(router).toBeDefined(); }); diff --git a/plugins/proxy-backend/src/service/router.ts b/plugins/proxy-backend/src/service/router.ts index b71299ef7e..5273b2c806 100644 --- a/plugins/proxy-backend/src/service/router.ts +++ b/plugins/proxy-backend/src/service/router.ts @@ -17,21 +17,57 @@ import { Config } from '@backstage/config'; import express from 'express'; import Router from 'express-promise-router'; -import createProxyMiddleware from 'http-proxy-middleware'; +import createProxyMiddleware, { + Config as ProxyConfig, + Proxy, +} from 'http-proxy-middleware'; import { Logger } from 'winston'; export interface RouterOptions { logger: Logger; config: Config; + // The URL path prefix that the router itself is mounted as, commonly "/proxy" + pathPrefix: string; +} + +// Creates a proxy middleware, possibly with defaults added on top of the +// given config. +function buildMiddleware( + pathPrefix: string, + route: string, + config: string | ProxyConfig, +): Proxy { + const fullConfig = + typeof config === 'string' ? { target: config } : { ...config }; + + // Default is to do a path rewrite that strips out the proxy's path prefix + // and the rest of the route. + if (fullConfig.pathRewrite === undefined) { + const routeWithSlash = route.endsWith('/') ? route : `${route}/`; + fullConfig.pathRewrite = { + [`^${pathPrefix}${routeWithSlash}`]: '/', + }; + } + + // Default is to update the Host header to the target + if (fullConfig.changeOrigin === undefined) { + fullConfig.changeOrigin = true; + } + + return createProxyMiddleware(fullConfig); } export async function createRouter( options: RouterOptions, ): Promise { const router = Router(); - const proxyConfig = options.config.get('proxy') ?? {}; + + const proxyConfig = options.config.getOptional('proxy') ?? {}; Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => { - router.use(route, createProxyMiddleware(proxyRouteConfig)); + router.use( + route, + buildMiddleware(options.pathPrefix, route, proxyRouteConfig), + ); }); return router; diff --git a/plugins/proxy-backend/src/service/standaloneServer.ts b/plugins/proxy-backend/src/service/standaloneServer.ts index f75c044f0f..68e9ade183 100644 --- a/plugins/proxy-backend/src/service/standaloneServer.ts +++ b/plugins/proxy-backend/src/service/standaloneServer.ts @@ -40,6 +40,7 @@ export async function startStandaloneServer( const router = await createRouter({ config, logger, + pathPrefix: '/proxy', }); const service = createServiceBuilder(module) .enableCors({ origin: 'http://localhost:3000' })