backend-app-api: add root http router service + adapt http router service
Co-authored-by: blam <ben@blam.sh> Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -8,11 +8,13 @@ import { CacheService } from '@backstage/backend-plugin-api';
|
||||
import { ConfigService } from '@backstage/backend-plugin-api';
|
||||
import { DatabaseService } from '@backstage/backend-plugin-api';
|
||||
import { ExtensionPoint } from '@backstage/backend-plugin-api';
|
||||
import { Handler } from 'express';
|
||||
import { HttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import { LifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { PermissionsService } from '@backstage/backend-plugin-api';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import { RootHttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import { RootLifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { RootLoggerService } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api';
|
||||
@@ -69,7 +71,7 @@ export const httpRouterFactory: (
|
||||
|
||||
// @public (undocumented)
|
||||
export type HttpRouterFactoryOptions = {
|
||||
indexPlugin?: string;
|
||||
pathPrefix?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -87,6 +89,17 @@ export const permissionsFactory: (
|
||||
options?: undefined,
|
||||
) => ServiceFactory<PermissionsService>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const rootHttpRouterFactory: (
|
||||
options?: RootHttpRouterFactoryOptions | undefined,
|
||||
) => ServiceFactory<RootHttpRouterService>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type RootHttpRouterFactoryOptions = {
|
||||
indexPath?: string | false;
|
||||
middleware?: Handler[];
|
||||
};
|
||||
|
||||
// @public
|
||||
export const rootLifecycleFactory: (
|
||||
options?: undefined,
|
||||
|
||||
@@ -18,49 +18,33 @@ import {
|
||||
createServiceFactory,
|
||||
coreServices,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import Router from 'express-promise-router';
|
||||
import { Handler } from 'express';
|
||||
import { createServiceBuilder } from '@backstage/backend-common';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type HttpRouterFactoryOptions = {
|
||||
/**
|
||||
* The plugin ID used for the index route. Defaults to 'app'
|
||||
* The path prefix used for each plugin, defaults to `/api/`.
|
||||
*/
|
||||
indexPlugin?: string;
|
||||
pathPrefix?: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const httpRouterFactory = createServiceFactory({
|
||||
service: coreServices.httpRouter,
|
||||
deps: {
|
||||
config: coreServices.config,
|
||||
plugin: coreServices.pluginMetadata,
|
||||
rootHttpRouter: coreServices.rootHttpRouter,
|
||||
},
|
||||
async factory({ config }, options?: HttpRouterFactoryOptions) {
|
||||
const defaultPluginId = options?.indexPlugin ?? 'app';
|
||||
|
||||
const apiRouter = Router();
|
||||
const rootRouter = Router();
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.loadConfig(config)
|
||||
.addRouter('/api', apiRouter)
|
||||
.addRouter('', rootRouter);
|
||||
|
||||
await service.start();
|
||||
async factory({ rootHttpRouter }, options?: HttpRouterFactoryOptions) {
|
||||
const pathPrefix = options?.pathPrefix ?? '/api/';
|
||||
|
||||
return async ({ plugin }) => {
|
||||
const pluginId = plugin.getId();
|
||||
const path = pathPrefix + plugin.getId();
|
||||
return {
|
||||
use(handler: Handler) {
|
||||
if (pluginId === defaultPluginId) {
|
||||
rootRouter.use(handler);
|
||||
} else {
|
||||
apiRouter.use(`/${pluginId}`, handler);
|
||||
}
|
||||
rootHttpRouter.use(path, handler);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,6 +25,8 @@ export { schedulerFactory } from './schedulerService';
|
||||
export { tokenManagerFactory } from './tokenManagerService';
|
||||
export { urlReaderFactory } from './urlReaderService';
|
||||
export { httpRouterFactory } from './httpRouterService';
|
||||
export { rootHttpRouterFactory } from './rootHttpRouterService';
|
||||
export { lifecycleFactory } from './lifecycleService';
|
||||
export { rootLifecycleFactory } from './rootLifecycleService';
|
||||
export type { HttpRouterFactoryOptions } from './httpRouterService';
|
||||
export type { RootHttpRouterFactoryOptions } from './rootHttpRouterService';
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { findConflictingPath } from './rootHttpRouterService';
|
||||
|
||||
describe('findConflictingPath', () => {
|
||||
it('finds conflicts when present', () => {
|
||||
expect(findConflictingPath(['/a'], '/a')).toBe('/a');
|
||||
expect(findConflictingPath(['/b'], '/a')).toBe(undefined);
|
||||
expect(findConflictingPath(['/a'], '/a/b')).toBe('/a');
|
||||
expect(findConflictingPath(['/a'], '/aa/b')).toBe(undefined);
|
||||
expect(findConflictingPath(['/aa'], '/a/b')).toBe(undefined);
|
||||
expect(findConflictingPath(['/a/b'], '/a')).toBe('/a/b');
|
||||
expect(findConflictingPath(['/a/b'], '/aa')).toBe(undefined);
|
||||
expect(findConflictingPath(['/b/a'], '/a')).toBe(undefined);
|
||||
expect(findConflictingPath(['/a'], '/aa')).toBe(undefined);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
createServiceFactory,
|
||||
coreServices,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import Router from 'express-promise-router';
|
||||
import { Handler } from 'express';
|
||||
import { createServiceBuilder } from '@backstage/backend-common';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type RootHttpRouterFactoryOptions = {
|
||||
/**
|
||||
* The path to forward all unmatched requests to. Defaults to '/api/app'
|
||||
*/
|
||||
indexPath?: string | false;
|
||||
|
||||
/**
|
||||
* Middlewares that are added before all other routes.
|
||||
*/
|
||||
middleware?: Handler[];
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export const rootHttpRouterFactory = createServiceFactory({
|
||||
service: coreServices.rootHttpRouter,
|
||||
deps: {
|
||||
config: coreServices.config,
|
||||
},
|
||||
async factory({ config }, options?: RootHttpRouterFactoryOptions) {
|
||||
const indexPath = options?.indexPath ?? '/api/app';
|
||||
|
||||
const namedRouter = Router();
|
||||
const indexRouter = Router();
|
||||
|
||||
const service = createServiceBuilder(module).loadConfig(config);
|
||||
|
||||
for (const middleware of options?.middleware ?? []) {
|
||||
service.addRouter('', middleware);
|
||||
}
|
||||
|
||||
service.addRouter('', namedRouter).addRouter('', indexRouter);
|
||||
|
||||
await service.start();
|
||||
|
||||
const existingPaths = new Array<string>();
|
||||
|
||||
return {
|
||||
use: (path: string, handler: Handler) => {
|
||||
const conflictingPath = findConflictingPath(existingPaths, path);
|
||||
if (conflictingPath) {
|
||||
throw new Error(
|
||||
`Path ${path} conflicts with the existing path ${conflictingPath}`,
|
||||
);
|
||||
}
|
||||
existingPaths.push(path);
|
||||
namedRouter.use(path, handler);
|
||||
|
||||
if (indexPath === path) {
|
||||
indexRouter.use(handler);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
function normalizePath(path: string): string {
|
||||
return path.replace(/\/*$/, '/');
|
||||
}
|
||||
|
||||
export function findConflictingPath(
|
||||
paths: string[],
|
||||
newPath: string,
|
||||
): string | undefined {
|
||||
const normalizedNewPath = normalizePath(newPath);
|
||||
for (const path of paths) {
|
||||
const normalizedPath = normalizePath(path);
|
||||
if (normalizedPath.startsWith(normalizedNewPath)) {
|
||||
return path;
|
||||
}
|
||||
if (normalizedNewPath.startsWith(normalizedPath)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user