backend-app-api: move http options reading to lib + refactor
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Config } from '@backstage/config';
|
||||
import { HttpServerOptions } from './types';
|
||||
|
||||
const DEFAULT_PORT = 7007;
|
||||
const DEFAULT_HOST = '';
|
||||
|
||||
/**
|
||||
* Reads {@link HttpServerOptions} from a {@link @backstage/config#Config} object.
|
||||
*
|
||||
* @public
|
||||
* @remarks
|
||||
*
|
||||
* The provided configuration object should contain the `listen` and
|
||||
* additional keys directly.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const opts = readHttpServerOptions(config.getConfig('backend'));
|
||||
* ```
|
||||
*/
|
||||
export function readHttpServerOptions(config: Config): HttpServerOptions {
|
||||
return {
|
||||
listen: readHttpListenOptions(config),
|
||||
https: readHttpsOptions(config),
|
||||
};
|
||||
}
|
||||
|
||||
function readHttpListenOptions(config: Config): HttpServerOptions['listen'] {
|
||||
const listen = config.get('listen');
|
||||
if (typeof listen === 'string') {
|
||||
const parts = listen.split(':');
|
||||
const port = parseInt(parts[parts.length - 1], 10);
|
||||
if (!isNaN(port)) {
|
||||
if (parts.length === 1) {
|
||||
return { port, host: DEFAULT_HOST };
|
||||
}
|
||||
if (parts.length === 2) {
|
||||
return { host: parts[0], port };
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
`Unable to parse listen address ${listen}, expected <port> or <host>:<port>`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
port: config.getOptionalNumber('listen.port') ?? DEFAULT_PORT,
|
||||
host: config.getOptionalString('listen.host') ?? DEFAULT_HOST,
|
||||
};
|
||||
}
|
||||
|
||||
function readHttpsOptions(config: Config): HttpServerOptions['https'] {
|
||||
const https = config.getOptional('https');
|
||||
if (https === true) {
|
||||
const baseUrl = config.getString('baseUrl');
|
||||
let hostname;
|
||||
try {
|
||||
hostname = new URL(baseUrl).hostname;
|
||||
} catch (error) {
|
||||
throw new Error(`Invalid baseUrl "${baseUrl}"`);
|
||||
}
|
||||
|
||||
return { certificate: { type: 'generated', hostname } };
|
||||
}
|
||||
|
||||
const cc = config.getOptionalConfig('https');
|
||||
if (!cc) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
certificate: {
|
||||
type: 'plain',
|
||||
cert: cc.getString('certificate.cert'),
|
||||
key: cc.getString('certificate.key'),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
export { readHttpServerOptions } from './config';
|
||||
export type { HttpServerOptions, HttpServerCertificateOptions } from './types';
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for starting up an HTTP server.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type HttpServerOptions = {
|
||||
listen: {
|
||||
port: number;
|
||||
host: string;
|
||||
};
|
||||
https?: {
|
||||
certificate: HttpServerCertificateOptions;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for configuring HTTPS for an HTTP server.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type HttpServerCertificateOptions =
|
||||
| {
|
||||
type: 'plain';
|
||||
key: string;
|
||||
cert: string;
|
||||
}
|
||||
| {
|
||||
type: 'generated';
|
||||
hostname: string;
|
||||
};
|
||||
@@ -52,49 +52,6 @@ type CustomOrigin = (
|
||||
callback: (err: Error | null, origin?: StaticOrigin) => void,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Reads some base options out of a config object.
|
||||
*
|
||||
* @param config - The root of a backend config object
|
||||
* @returns A base options object
|
||||
*
|
||||
* @example
|
||||
* ```json
|
||||
* {
|
||||
* baseUrl: "http://localhost:7007",
|
||||
* listen: "0.0.0.0:7007"
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function readBaseOptions(config: Config): BaseOptions {
|
||||
if (typeof config.get('listen') === 'string') {
|
||||
// TODO(freben): Expand this to support more addresses and perhaps optional
|
||||
const { host, port } = parseListenAddress(config.getString('listen'));
|
||||
|
||||
return removeUnknown({
|
||||
listenPort: port,
|
||||
listenHost: host,
|
||||
});
|
||||
}
|
||||
|
||||
const port = config.getOptional('listen.port');
|
||||
if (
|
||||
typeof port !== 'undefined' &&
|
||||
typeof port !== 'number' &&
|
||||
typeof port !== 'string'
|
||||
) {
|
||||
throw new Error(
|
||||
`Invalid type in config for key 'backend.listen.port', got ${typeof port}, wanted string or number`,
|
||||
);
|
||||
}
|
||||
|
||||
return removeUnknown({
|
||||
listenPort: port,
|
||||
listenHost: config.getOptionalString('listen.host'),
|
||||
baseUrl: config.getOptionalString('baseUrl'),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read a CORS options object from the root of a config object.
|
||||
*
|
||||
@@ -165,49 +122,6 @@ export function readCspOptions(
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read a https settings object from the root of a config object.
|
||||
*
|
||||
* @param config - The root of a backend config object
|
||||
* @returns A https settings object, or undefined if not specified
|
||||
*
|
||||
* @example
|
||||
* ```json
|
||||
* {
|
||||
* https: {
|
||||
* certificate: ...
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function readHttpsSettings(config: Config): HttpsSettings | undefined {
|
||||
const https = config.getOptional('https');
|
||||
if (https === true) {
|
||||
const baseUrl = config.getString('baseUrl');
|
||||
let hostname;
|
||||
try {
|
||||
hostname = new URL(baseUrl).hostname;
|
||||
} catch (error) {
|
||||
throw new Error(`Invalid backend.baseUrl "${baseUrl}"`);
|
||||
}
|
||||
|
||||
return { certificate: { hostname } };
|
||||
}
|
||||
|
||||
const cc = config.getOptionalConfig('https');
|
||||
if (!cc) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const certificateConfig = cc.get('certificate');
|
||||
|
||||
const cfg = {
|
||||
certificate: certificateConfig,
|
||||
};
|
||||
|
||||
return removeUnknown(cfg as HttpsSettings);
|
||||
}
|
||||
|
||||
function getOptionalStringOrStrings(
|
||||
config: Config,
|
||||
key: string,
|
||||
@@ -269,16 +183,3 @@ function removeUnknown<T extends object>(obj: T): T {
|
||||
Object.entries(obj).filter(([, v]) => v !== undefined),
|
||||
) as T;
|
||||
}
|
||||
|
||||
function parseListenAddress(value: string): { host?: string; port?: number } {
|
||||
const parts = value.split(':');
|
||||
if (parts.length === 1) {
|
||||
return { port: parseInt(parts[0], 10) };
|
||||
}
|
||||
if (parts.length === 2) {
|
||||
return { host: parts[0], port: parseInt(parts[1], 10) };
|
||||
}
|
||||
throw new Error(
|
||||
`Unable to parse listen address ${value}, expected <port> or <host>:<port>`,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user