From 461a6c0f9345110de23beee3381eac8f4376da03 Mon Sep 17 00:00:00 2001 From: Todd Kennedy Date: Wed, 29 May 2019 13:00:56 -0700 Subject: [PATCH] [feat] create additional http servers (#36804) * [feat] create additional http servers allow for additional http servers to be created, tracked and returned * respond to pr feedback * tweak test * update documentation * destructure port, remove unnecessary imports * [fix] export correct type * [feat] expose createNewServer to plugins * [fix] respond to pr feedback * todo: add schema validation & integration test * use reach * [fix] use validateKey to validate partial * [fix] change config shadowing * check kibana port & prevent shadowing * centralize start/stop for servers, add integration test * remove unnecessary property * never forget your await * remove option to pass config into start * fix pr feedback * fix documentation * fix test failures --- .../kibana-plugin-server.coresetup.http.md | 1 + .../server/kibana-plugin-server.coresetup.md | 2 +- ...server.httpservicesetup.createnewserver.md | 11 ++++ .../kibana-plugin-server.httpservicesetup.md | 11 +++- .../core/server/kibana-plugin-server.md | 2 +- src/core/server/http/http_server.test.ts | 60 +++++++++---------- src/core/server/http/http_server.ts | 12 ++-- src/core/server/http/http_service.mock.ts | 4 ++ .../server/http/http_service.test.mocks.ts | 11 +++- src/core/server/http/http_service.test.ts | 45 +++++++++++++- src/core/server/http/http_service.ts | 55 +++++++++++++++-- src/core/server/index.ts | 1 + src/core/server/plugins/plugin_context.ts | 1 + src/core/server/server.api.md | 9 ++- 14 files changed, 173 insertions(+), 52 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-server.httpservicesetup.createnewserver.md diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.http.md b/docs/development/core/server/kibana-plugin-server.coresetup.http.md index 8cb25af29e4ba..81e099b7828fe 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.http.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.http.md @@ -13,5 +13,6 @@ http: { registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; getBasePathFor: HttpServiceSetup['getBasePathFor']; setBasePathFor: HttpServiceSetup['setBasePathFor']; + createNewServer: HttpServiceSetup['createNewServer']; }; ``` diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index 7b46817842def..7be8599d563a7 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -17,5 +17,5 @@ export interface CoreSetup | Property | Type | Description | | --- | --- | --- | | [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | {`

` adminClient$: Observable<ClusterClient>;`

` dataClient$: Observable<ClusterClient>;`

` } | | -| [http](./kibana-plugin-server.coresetup.http.md) | {`

` registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];`

` registerAuth: HttpServiceSetup['registerAuth'];`

` registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];`

` getBasePathFor: HttpServiceSetup['getBasePathFor'];`

` setBasePathFor: HttpServiceSetup['setBasePathFor'];`

` } | | +| [http](./kibana-plugin-server.coresetup.http.md) | {`

` registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];`

` registerAuth: HttpServiceSetup['registerAuth'];`

` registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];`

` getBasePathFor: HttpServiceSetup['getBasePathFor'];`

` setBasePathFor: HttpServiceSetup['setBasePathFor'];`

` createNewServer: HttpServiceSetup['createNewServer'];`

` } | | diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.createnewserver.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.createnewserver.md new file mode 100644 index 0000000000000..e33f1c51abee9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.createnewserver.md @@ -0,0 +1,11 @@ + + +[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) > [createNewServer](./kibana-plugin-server.httpservicesetup.createnewserver.md) + +## HttpServiceSetup.createNewServer property + +Signature: + +```typescript +createNewServer: (cfg: Partial) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md index 36335c0d5f4cd..a1b77ba466399 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md @@ -2,11 +2,18 @@ [Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) -## HttpServiceSetup type +## HttpServiceSetup interface Signature: ```typescript -export declare type HttpServiceSetup = HttpServerSetup; +export interface HttpServiceSetup extends HttpServerSetup ``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [createNewServer](./kibana-plugin-server.httpservicesetup.createnewserver.md) | (cfg: Partial<HttpConfig>) => Promise<HttpServerSetup> | | + diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 843f2a65e9562..0c03795c1b571 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -29,6 +29,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins start method. | | [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. | | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | +| [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | | | [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | | | [InternalCoreStart](./kibana-plugin-server.internalcorestart.md) | | | [Logger](./kibana-plugin-server.logger.md) | Logger exposes all the necessary methods to log any type of information and this is the interface used by the logging consumers including plugins. | @@ -49,7 +50,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md) | | | [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | | | [Headers](./kibana-plugin-server.headers.md) | | -| [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | | | [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | | | [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | | | [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The plugin export at the root of a plugin's server directory should conform to this interface. | diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 0f1a85a336b04..618fbfe7a2067 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -58,7 +58,7 @@ test('listening after started', async () => { expect(server.isListening()).toBe(false); await server.setup(config); - await server.start(config); + await server.start(); expect(server.isListening()).toBe(true); }); @@ -72,7 +72,7 @@ test('200 OK with body', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/foo/') @@ -92,7 +92,7 @@ test('202 Accepted with body', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/foo/') @@ -112,7 +112,7 @@ test('204 No content', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/foo/') @@ -134,7 +134,7 @@ test('400 Bad request with error', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/foo/') @@ -164,7 +164,7 @@ test('valid params', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/foo/some-string') @@ -194,7 +194,7 @@ test('invalid params', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/foo/some-string') @@ -227,7 +227,7 @@ test('valid query', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/foo/?bar=test&quux=123') @@ -257,7 +257,7 @@ test('invalid query', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/foo/?bar=test') @@ -290,7 +290,7 @@ test('valid body', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .post('/foo/') @@ -324,7 +324,7 @@ test('invalid body', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .post('/foo/') @@ -357,7 +357,7 @@ test('handles putting', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .put('/foo/') @@ -388,7 +388,7 @@ test('handles deleting', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .delete('/foo/3') @@ -414,7 +414,7 @@ test('filtered headers', async () => { const { registerRouter, server: innerServer } = await server.setup(config); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/foo/?bar=quux') @@ -444,10 +444,10 @@ describe('with `basepath: /bar` and `rewriteBasePath: false`', () => { res.ok({ key: 'value:/foo' }) ); - const { registerRouter, server: innerServer } = await server.setup(config); + const { registerRouter, server: innerServer } = await server.setup(configWithBasePath); registerRouter(router); - await server.start(configWithBasePath); + await server.start(); innerServerListener = innerServer.listener; }); @@ -508,7 +508,7 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => { const { registerRouter, server: innerServer } = await server.setup(configWithBasePath); registerRouter(router); - await server.start(configWithBasePath); + await server.start(); innerServerListener = innerServer.listener; }); @@ -571,10 +571,10 @@ describe('with defined `redirectHttpFromPort`', () => { const router = new Router('/'); router.get({ path: '/', validate: false }, async (req, res) => res.ok({ key: 'value:/' })); - const { registerRouter } = await server.setup(config); + const { registerRouter } = await server.setup(configWithSSL); registerRouter(router); - await server.start(configWithSSL); + await server.start(); }); }); @@ -610,7 +610,7 @@ test('registers registerOnPostAuth interceptor several times', async () => { }); test('throws an error if starts without set up', async () => { - await expect(server.start(config)).rejects.toThrowErrorMatchingInlineSnapshot( + await expect(server.start()).rejects.toThrowErrorMatchingInlineSnapshot( `"Http server is not setup up yet"` ); }); @@ -634,7 +634,7 @@ test('#getBasePathFor() returns base path associated with an incoming request', router.get({ path: '/', validate: false }, (req, res) => res.ok({ key: getBasePathFor(req) })); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/') .expect(200) @@ -668,7 +668,7 @@ test('#getBasePathFor() is based on server base path', async () => { ); registerRouter(router); - await server.start(configWithBasePath); + await server.start(); await supertest(innerServer.listener) .get('/') .expect(200) @@ -727,7 +727,7 @@ test('Should enable auth for a route by default if registerAuth has been called' .mockImplementation((req, sessionStorage, t) => t.authenticated({})); await registerAuth(authenticate, cookieOptions); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/') .expect(200); @@ -744,7 +744,7 @@ test('Should support disabling auth for a route', async () => { const authenticate = jest.fn(); await registerAuth(authenticate, cookieOptions); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/') .expect(200); @@ -764,7 +764,7 @@ describe('#auth.isAuthenticated()', () => { await registerAuth((req, sessionStorage, t) => t.authenticated({}), cookieOptions); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/') .expect(200, { isAuthenticated: true }); @@ -781,7 +781,7 @@ describe('#auth.isAuthenticated()', () => { await registerAuth((req, sessionStorage, t) => t.authenticated({}), cookieOptions); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/') .expect(200, { isAuthenticated: false }); @@ -796,7 +796,7 @@ describe('#auth.isAuthenticated()', () => { ); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/') .expect(200, { isAuthenticated: false }); @@ -815,7 +815,7 @@ describe('#auth.get()', () => { const router = new Router(''); router.get({ path: '/', validate: false }, async (req, res) => res.ok(auth.get(req))); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/') @@ -828,7 +828,7 @@ describe('#auth.get()', () => { router.get({ path: '/', validate: false }, async (req, res) => res.ok(auth.get(req))); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/') .expect(200, { status: 'unknown' }); @@ -845,7 +845,7 @@ describe('#auth.get()', () => { ); registerRouter(router); - await server.start(config); + await server.start(); await supertest(innerServer.listener) .get('/') diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index db2233b59a2e8..bdf02889841bb 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -73,6 +73,7 @@ export interface HttpServerSetup { export class HttpServer { private server?: Server; + private config?: HttpConfig; private registeredRouters = new Set(); private authRegistered = false; private basePathCache = new WeakMap< @@ -124,6 +125,7 @@ export class HttpServer { public setup(config: HttpConfig): HttpServerSetup { const serverOptions = getServerOptions(config); this.server = createServer(serverOptions); + this.config = config; this.setupBasePathRewrite(config); @@ -149,7 +151,7 @@ export class HttpServer { }; } - public async start(config: HttpConfig) { + public async start() { if (this.server === undefined) { throw new Error('Http server is not setup up yet'); } @@ -170,12 +172,8 @@ export class HttpServer { } await this.server.start(); - - this.log.debug( - `http server running at ${this.server.info.uri}${ - config.rewriteBasePath ? config.basePath : '' - }` - ); + const serverPath = this.config!.rewriteBasePath || this.config!.basePath || ''; + this.log.debug(`http server running at ${this.server.info.uri}${serverPath}`); } public async stop() { diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 6d49ac3010ef0..7dccacee8509a 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -19,6 +19,8 @@ import { Server, ServerOptions } from 'hapi'; import { HttpService } from './http_service'; +import { HttpConfig } from './http_config'; +import { HttpServerSetup } from './http_server'; const createSetupContractMock = () => { const setupContract = { @@ -35,6 +37,8 @@ const createSetupContractMock = () => { get: jest.fn(), isAuthenticated: jest.fn(), }, + createNewServer: async (cfg: Partial): Promise => + ({} as HttpServerSetup), }; return setupContract; }; diff --git a/src/core/server/http/http_service.test.mocks.ts b/src/core/server/http/http_service.test.mocks.ts index a0d7ff5069eb0..c147944f2b7d8 100644 --- a/src/core/server/http/http_service.test.mocks.ts +++ b/src/core/server/http/http_service.test.mocks.ts @@ -19,6 +19,11 @@ export const mockHttpServer = jest.fn(); -jest.mock('./http_server', () => ({ - HttpServer: mockHttpServer, -})); +jest.mock('./http_server', () => { + const realHttpServer = jest.requireActual('./http_server'); + + return { + ...realHttpServer, + HttpServer: mockHttpServer, + }; +}); diff --git a/src/core/server/http/http_service.test.ts b/src/core/server/http/http_service.test.ts index 7b3fd024b477c..16f946ffcc7ae 100644 --- a/src/core/server/http/http_service.test.ts +++ b/src/core/server/http/http_service.test.ts @@ -76,6 +76,46 @@ test('creates and sets up http server', async () => { expect(httpServer.start).toHaveBeenCalledTimes(1); }); +// this is an integration test! +test('creates and sets up second http server', async () => { + const configService = createConfigService({ + host: 'localhost', + port: 1234, + }); + const { HttpServer } = jest.requireActual('./http_server'); + + mockHttpServer.mockImplementation((...args) => new HttpServer(...args)); + + const service = new HttpService({ configService, env, logger }); + const serverSetup = await service.setup(); + const cfg = { port: 2345 }; + await serverSetup.createNewServer(cfg); + const server = await service.start(); + expect(server.isListening()).toBeTruthy(); + expect(server.isListening(cfg.port)).toBeTruthy(); + + try { + await serverSetup.createNewServer(cfg); + } catch (err) { + expect(err.message).toBe('port 2345 is already in use'); + } + + try { + await serverSetup.createNewServer({ port: 1234 }); + } catch (err) { + expect(err.message).toBe('port 1234 is already in use'); + } + + try { + await serverSetup.createNewServer({ host: 'example.org' }); + } catch (err) { + expect(err.message).toBe('port must be defined'); + } + await service.stop(); + expect(server.isListening()).toBeFalsy(); + expect(server.isListening(cfg.port)).toBeFalsy(); +}); + test('logs error if already set up', async () => { const configService = createConfigService(); @@ -153,8 +193,9 @@ test('returns http server contract on setup', async () => { })); const service = new HttpService({ configService, env, logger }); - - expect(await service.setup()).toBe(httpServer); + const { createNewServer, ...setupHttpServer } = await service.setup(); + expect(createNewServer).toBeDefined(); + expect(setupHttpServer).toEqual(httpServer); }); test('does not start http server if process is dev cluster master', async () => { diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index 3a9fcb7be54e7..fec3774e2f366 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -20,15 +20,18 @@ import { Observable, Subscription } from 'rxjs'; import { first, map } from 'rxjs/operators'; +import { LoggerFactory } from '../logging'; import { CoreService } from '../../types'; import { Logger } from '../logging'; import { CoreContext } from '../core_context'; -import { HttpConfig, HttpConfigType } from './http_config'; +import { HttpConfig, HttpConfigType, config as httpConfig } from './http_config'; import { HttpServer, HttpServerSetup } from './http_server'; import { HttpsRedirectServer } from './https_redirect_server'; /** @public */ -export type HttpServiceSetup = HttpServerSetup; +export interface HttpServiceSetup extends HttpServerSetup { + createNewServer: (cfg: Partial) => Promise; +} /** @public */ export interface HttpServiceStart { /** Indicates if http server is listening on a port */ @@ -38,13 +41,16 @@ export interface HttpServiceStart { /** @internal */ export class HttpService implements CoreService { private readonly httpServer: HttpServer; + private readonly secondaryServers: Map = new Map(); private readonly httpsRedirectServer: HttpsRedirectServer; private readonly config$: Observable; private configSubscription?: Subscription; + private readonly logger: LoggerFactory; private readonly log: Logger; constructor(private readonly coreContext: CoreContext) { + this.logger = coreContext.logger; this.log = coreContext.logger.get('http'); this.config$ = coreContext.configService .atPath('server') @@ -69,7 +75,12 @@ export class HttpService implements CoreService server.start())); } return { - isListening: () => this.httpServer.isListening(), + isListening: (port = 0) => { + const server = this.secondaryServers.get(port); + if (server) return server.isListening(); + return this.httpServer.isListening(); + }, }; } + private async createServer(cfg: Partial) { + const { port } = cfg; + const config = await this.config$.pipe(first()).toPromise(); + + if (!port) { + throw new Error('port must be defined'); + } + + // verify that main server and none of the secondary servers are already using this port + if (this.secondaryServers.has(port) || config.port === port) { + throw new Error(`port ${port} is already in use`); + } + + for (const [key, val] of Object.entries(cfg)) { + httpConfig.schema.validateKey(key, val); + } + + const baseConfig = await this.config$.pipe(first()).toPromise(); + const finalConfig = { ...baseConfig, ...cfg }; + const log = this.logger.get('http', `server:${port}`); + + const httpServer = new HttpServer(log); + const httpSetup = await httpServer.setup(finalConfig); + this.secondaryServers.set(port, httpServer); + return httpSetup; + } + public async stop() { if (this.configSubscription === undefined) { return; @@ -104,5 +147,7 @@ export class HttpService implements CoreService s.stop())); + this.secondaryServers.clear(); } } diff --git a/src/core/server/index.ts b/src/core/server/index.ts index e144c0f2568f8..e8e3563b7ca3a 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -86,6 +86,7 @@ export interface CoreSetup { registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; getBasePathFor: HttpServiceSetup['getBasePathFor']; setBasePathFor: HttpServiceSetup['setBasePathFor']; + createNewServer: HttpServiceSetup['createNewServer']; }; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index b2a88bbd32760..9dfc2df1c2d20 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -122,6 +122,7 @@ export function createPluginSetupContext( registerOnPostAuth: deps.http.registerOnPostAuth, getBasePathFor: deps.http.getBasePathFor, setBasePathFor: deps.http.setBasePathFor, + createNewServer: deps.http.createNewServer, }, }; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 745fbdb7d9d99..2d2472103e5ab 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -4,6 +4,7 @@ ```ts +import { ByteSizeValue } from '@kbn/config-schema'; import { ConfigOptions } from 'elasticsearch'; import { Duration } from 'moment'; import { ObjectType } from '@kbn/config-schema'; @@ -88,6 +89,7 @@ export interface CoreSetup { registerOnPostAuth: HttpServiceSetup['registerOnPostAuth']; getBasePathFor: HttpServiceSetup['getBasePathFor']; setBasePathFor: HttpServiceSetup['setBasePathFor']; + createNewServer: HttpServiceSetup['createNewServer']; }; } @@ -133,7 +135,12 @@ export type Headers = Record; // Warning: (ae-forgotten-export) The symbol "HttpServerSetup" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type HttpServiceSetup = HttpServerSetup; +export interface HttpServiceSetup extends HttpServerSetup { + // Warning: (ae-forgotten-export) The symbol "HttpConfig" needs to be exported by the entry point index.d.ts + // + // (undocumented) + createNewServer: (cfg: Partial) => Promise; +} // @public (undocumented) export interface HttpServiceStart {