mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* [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 * [fix] failing negation on merge
This commit is contained in:
parent
95c362805e
commit
c4a9ade6ac
14 changed files with 244 additions and 126 deletions
|
@ -12,5 +12,6 @@ http: {
|
|||
registerOnRequest: HttpServiceSetup['registerOnRequest'];
|
||||
getBasePathFor: HttpServiceSetup['getBasePathFor'];
|
||||
setBasePathFor: HttpServiceSetup['setBasePathFor'];
|
||||
createNewServer: HttpServiceSetup['createNewServer'];
|
||||
};
|
||||
```
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md)
|
||||
|
||||
## CoreSetup interface
|
||||
|
||||
Context passed to the plugins `setup` method.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface CoreSetup
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | <code>{`<p/>` adminClient$: Observable<ClusterClient>;`<p/>` dataClient$: Observable<ClusterClient>;`<p/>` }</code> | |
|
||||
| [http](./kibana-plugin-server.coresetup.http.md) | <code>{`<p/>` registerAuth: HttpServiceSetup['registerAuth'];`<p/>` registerOnRequest: HttpServiceSetup['registerOnRequest'];`<p/>` getBasePathFor: HttpServiceSetup['getBasePathFor'];`<p/>` setBasePathFor: HttpServiceSetup['setBasePathFor'];`<p/>` }</code> | |
|
||||
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md)
|
||||
|
||||
## CoreSetup interface
|
||||
|
||||
Context passed to the plugins `setup` method.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface CoreSetup
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
||||
| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | <code>{`<p/>` adminClient\$: Observable<ClusterClient>;`<p/>` dataClient\$: Observable<ClusterClient>;`<p/>` }</code> | |
|
||||
| [http](./kibana-plugin-server.coresetup.http.md) | <code>{`<p/>` registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];`<p/>` registerAuth: HttpServiceSetup['registerAuth'];`<p/>` registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];`<p/>` getBasePathFor: HttpServiceSetup['getBasePathFor'];`<p/>` setBasePathFor: HttpServiceSetup['setBasePathFor'];`<p/>` createNewServer: HttpServiceSetup['createNewServer'];`<p/>` }</code> | |
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
createNewServer: (cfg: Partial<HttpConfig>) => Promise<HttpServerSetup>;
|
||||
```
|
|
@ -2,11 +2,18 @@
|
|||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md)
|
||||
|
||||
## HttpServiceSetup type
|
||||
## HttpServiceSetup interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type HttpServiceSetup = HttpServerSetup;
|
||||
export interface HttpServiceSetup extends HttpServerSetup
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [createNewServer](./kibana-plugin-server.httpservicesetup.createnewserver.md) | <code>(cfg: Partial<HttpConfig>) => Promise<HttpServerSetup></code> | |
|
||||
|
||||
|
|
|
@ -1,55 +1,55 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md)
|
||||
|
||||
## kibana-plugin-server package
|
||||
|
||||
The Kibana Core APIs for server-side plugins.
|
||||
|
||||
A plugin's `server/index` file must contain a named import, `plugin`<!-- -->, that implements [PluginInitializer](./kibana-plugin-server.plugininitializer.md) which returns an object that implements [Plugin](./kibana-plugin-server.plugin.md)<!-- -->.
|
||||
|
||||
The plugin integrates with the core system via lifecycle events: `setup`<!-- -->, `start`<!-- -->, and `stop`<!-- -->. In each lifecycle method, the plugin will receive the corresponding core services available (either [CoreSetup](./kibana-plugin-server.coresetup.md) or [CoreStart](./kibana-plugin-server.corestart.md)<!-- -->) and any interfaces returned by dependency plugins' lifecycle method. Anything returned by the plugin's lifecycle method will be exposed to downstream dependencies when their corresponding lifecycle methods are invoked.
|
||||
|
||||
## Classes
|
||||
|
||||
| Class | Description |
|
||||
| --- | --- |
|
||||
| [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via <code>asScoped(...)</code>). |
|
||||
| [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | |
|
||||
| [Router](./kibana-plugin-server.router.md) | |
|
||||
| [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" <code>ClusterClient</code> but exposes additional <code>callAsCurrentUser</code> method that doesn't use credentials of the Kibana internal user (as <code>callAsInternalUser</code> does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API |
|
||||
|
||||
## Interfaces
|
||||
|
||||
| Interface | Description |
|
||||
| --- | --- |
|
||||
| [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. |
|
||||
| [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. |
|
||||
| [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins <code>setup</code> method. |
|
||||
| [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins <code>start</code> 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) | |
|
||||
| [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. |
|
||||
| [LoggerFactory](./kibana-plugin-server.loggerfactory.md) | The single purpose of <code>LoggerFactory</code> interface is to define a way to retrieve a context-based logger instance. |
|
||||
| [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata |
|
||||
| [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) | A tool set defining an outcome of OnRequest interceptor for incoming request. |
|
||||
| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
|
||||
| [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. |
|
||||
| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | |
|
||||
| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | |
|
||||
|
||||
## Type Aliases
|
||||
|
||||
| Type Alias | Description |
|
||||
| --- | --- |
|
||||
| [APICaller](./kibana-plugin-server.apicaller.md) | |
|
||||
| [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md) | |
|
||||
| [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | |
|
||||
| [Headers](./kibana-plugin-server.headers.md) | |
|
||||
| [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | |
|
||||
| [OnRequestHandler](./kibana-plugin-server.onrequesthandler.md) | |
|
||||
| [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>server</code> directory should conform to this interface. |
|
||||
| [PluginName](./kibana-plugin-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. |
|
||||
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md)
|
||||
|
||||
## kibana-plugin-server package
|
||||
|
||||
The Kibana Core APIs for server-side plugins.
|
||||
|
||||
A plugin's `server/index` file must contain a named import, `plugin`<!-- -->, that implements [PluginInitializer](./kibana-plugin-server.plugininitializer.md) which returns an object that implements [Plugin](./kibana-plugin-server.plugin.md)<!-- -->.
|
||||
|
||||
The plugin integrates with the core system via lifecycle events: `setup`<!-- -->, `start`<!-- -->, and `stop`<!-- -->. In each lifecycle method, the plugin will receive the corresponding core services available (either [CoreSetup](./kibana-plugin-server.coresetup.md) or [CoreStart](./kibana-plugin-server.corestart.md)<!-- -->) and any interfaces returned by dependency plugins' lifecycle method. Anything returned by the plugin's lifecycle method will be exposed to downstream dependencies when their corresponding lifecycle methods are invoked.
|
||||
|
||||
## Classes
|
||||
|
||||
| Class | Description |
|
||||
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [ClusterClient](./kibana-plugin-server.clusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via <code>asScoped(...)</code>). |
|
||||
| [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | |
|
||||
| [Router](./kibana-plugin-server.router.md) | |
|
||||
| [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" <code>ClusterClient</code> but exposes additional <code>callAsCurrentUser</code> method that doesn't use credentials of the Kibana internal user (as <code>callAsInternalUser</code> does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API |
|
||||
|
||||
## Interfaces
|
||||
|
||||
| Interface | Description |
|
||||
| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. |
|
||||
| [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. |
|
||||
| [CoreSetup](./kibana-plugin-server.coresetup.md) | Context passed to the plugins <code>setup</code> method. |
|
||||
| [CoreStart](./kibana-plugin-server.corestart.md) | Context passed to the plugins <code>start</code> 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. |
|
||||
| [LoggerFactory](./kibana-plugin-server.loggerfactory.md) | The single purpose of <code>LoggerFactory</code> interface is to define a way to retrieve a context-based logger instance. |
|
||||
| [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata |
|
||||
| [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) | A tool set defining an outcome of OnRequest interceptor for incoming request. |
|
||||
| [Plugin](./kibana-plugin-server.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
|
||||
| [PluginInitializerContext](./kibana-plugin-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. |
|
||||
| [PluginsServiceSetup](./kibana-plugin-server.pluginsservicesetup.md) | |
|
||||
| [PluginsServiceStart](./kibana-plugin-server.pluginsservicestart.md) | |
|
||||
|
||||
## Type Aliases
|
||||
|
||||
| Type Alias | Description |
|
||||
| -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [APICaller](./kibana-plugin-server.apicaller.md) | |
|
||||
| [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md) | |
|
||||
| [ElasticsearchClientConfig](./kibana-plugin-server.elasticsearchclientconfig.md) | |
|
||||
| [Headers](./kibana-plugin-server.headers.md) | |
|
||||
| [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | |
|
||||
| [OnRequestHandler](./kibana-plugin-server.onrequesthandler.md) | |
|
||||
| [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>server</code> directory should conform to this interface. |
|
||||
| [PluginName](./kibana-plugin-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. |
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
@ -489,8 +489,8 @@ describe('with `basepath: /bar` and `rewriteBasePath: false`', () => {
|
|||
});
|
||||
|
||||
describe('with `basepath: /bar` and `rewriteBasePath: true`', () => {
|
||||
let configWithBasePath: HttpConfig;
|
||||
let innerServerListener: Server;
|
||||
let configWithBasePath: HttpConfig;
|
||||
|
||||
beforeEach(async () => {
|
||||
configWithBasePath = {
|
||||
|
@ -498,17 +498,16 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => {
|
|||
basePath: '/bar',
|
||||
rewriteBasePath: true,
|
||||
} as HttpConfig;
|
||||
|
||||
const router = new Router('/');
|
||||
router.get({ path: '/', validate: false }, async (req, res) => res.ok({ key: 'value:/' }));
|
||||
router.get({ path: '/foo', validate: false }, async (req, res) =>
|
||||
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;
|
||||
});
|
||||
|
||||
|
@ -571,10 +570,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 +609,7 @@ test('registers onRequest 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 +633,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 +667,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)
|
||||
|
|
|
@ -55,6 +55,7 @@ export interface HttpServerSetup {
|
|||
|
||||
export class HttpServer {
|
||||
private server?: Server;
|
||||
private config?: HttpConfig;
|
||||
private registeredRouters = new Set<Router>();
|
||||
private authRegistered = false;
|
||||
private basePathCache = new WeakMap<
|
||||
|
@ -102,6 +103,7 @@ export class HttpServer {
|
|||
public setup(config: HttpConfig): HttpServerSetup {
|
||||
const serverOptions = getServerOptions(config);
|
||||
this.server = createServer(serverOptions);
|
||||
this.config = config;
|
||||
|
||||
return {
|
||||
options: serverOptions,
|
||||
|
@ -120,13 +122,13 @@ 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');
|
||||
}
|
||||
this.log.debug('starting http server');
|
||||
|
||||
this.setupBasePathRewrite(this.server, config);
|
||||
this.setupBasePathRewrite(this.server);
|
||||
|
||||
for (const router of this.registeredRouters) {
|
||||
for (const route of router.getRoutes()) {
|
||||
|
@ -139,12 +141,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() {
|
||||
|
@ -157,12 +155,12 @@ export class HttpServer {
|
|||
this.server = undefined;
|
||||
}
|
||||
|
||||
private setupBasePathRewrite(server: Server, config: HttpConfig) {
|
||||
if (config.basePath === undefined || !config.rewriteBasePath) {
|
||||
private setupBasePathRewrite(server: Server) {
|
||||
if (this.config!.basePath === undefined || !this.config!.rewriteBasePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const basePath = config.basePath;
|
||||
const basePath = this.config!.basePath;
|
||||
server.ext('onRequest', (request, responseToolkit) => {
|
||||
const newURL = modifyUrl(request.url.href!, urlParts => {
|
||||
if (urlParts.pathname != null && urlParts.pathname.startsWith(basePath)) {
|
||||
|
@ -171,7 +169,6 @@ export class HttpServer {
|
|||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
if (!newURL) {
|
||||
return responseToolkit
|
||||
.response('Not Found')
|
||||
|
|
|
@ -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 = {
|
||||
|
@ -30,6 +32,8 @@ const createSetupContractMock = () => {
|
|||
setBasePathFor: jest.fn(),
|
||||
// we can mock some hapi server method when we need it
|
||||
server: {} as Server,
|
||||
createNewServer: async (cfg: Partial<HttpConfig>): Promise<HttpServerSetup> =>
|
||||
({} as HttpServerSetup),
|
||||
};
|
||||
return setupContract;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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<HttpConfig>) => Promise<HttpServerSetup>;
|
||||
}
|
||||
/** @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<HttpServiceSetup, HttpServiceStart> {
|
||||
private readonly httpServer: HttpServer;
|
||||
private readonly secondaryServers: Map<number, HttpServer> = new Map();
|
||||
private readonly httpsRedirectServer: HttpsRedirectServer;
|
||||
private readonly config$: Observable<HttpConfig>;
|
||||
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<HttpConfigType>('server')
|
||||
|
@ -69,7 +75,12 @@ export class HttpService implements CoreService<HttpServiceSetup, HttpServiceSta
|
|||
|
||||
const config = await this.config$.pipe(first()).toPromise();
|
||||
|
||||
return this.httpServer.setup(config);
|
||||
const httpSetup = (this.httpServer.setup(config) || {}) as HttpServiceSetup;
|
||||
const setup = {
|
||||
...httpSetup,
|
||||
...{ createNewServer: this.createServer.bind(this) },
|
||||
};
|
||||
return setup;
|
||||
}
|
||||
|
||||
public async start() {
|
||||
|
@ -86,14 +97,46 @@ export class HttpService implements CoreService<HttpServiceSetup, HttpServiceSta
|
|||
await this.httpsRedirectServer.start(config);
|
||||
}
|
||||
|
||||
await this.httpServer.start(config);
|
||||
await this.httpServer.start();
|
||||
await Promise.all([...this.secondaryServers.values()].map(server => 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<HttpConfig>) {
|
||||
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<HttpServiceSetup, HttpServiceSta
|
|||
|
||||
await this.httpServer.stop();
|
||||
await this.httpsRedirectServer.stop();
|
||||
await Promise.all([...this.secondaryServers.values()].map(s => s.stop()));
|
||||
this.secondaryServers.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ export interface CoreSetup {
|
|||
registerOnRequest: HttpServiceSetup['registerOnRequest'];
|
||||
getBasePathFor: HttpServiceSetup['getBasePathFor'];
|
||||
setBasePathFor: HttpServiceSetup['setBasePathFor'];
|
||||
createNewServer: HttpServiceSetup['createNewServer'];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -121,6 +121,7 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
|
|||
registerOnRequest: deps.http.registerOnRequest,
|
||||
getBasePathFor: deps.http.getBasePathFor,
|
||||
setBasePathFor: deps.http.setBasePathFor,
|
||||
createNewServer: deps.http.createNewServer,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
@ -87,6 +88,7 @@ export interface CoreSetup {
|
|||
registerOnRequest: HttpServiceSetup['registerOnRequest'];
|
||||
getBasePathFor: HttpServiceSetup['getBasePathFor'];
|
||||
setBasePathFor: HttpServiceSetup['setBasePathFor'];
|
||||
createNewServer: HttpServiceSetup['createNewServer'];
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -132,7 +134,12 @@ export type Headers = Record<string, string | string[] | undefined>;
|
|||
// 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<HttpConfig>) => Promise<HttpServerSetup>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface HttpServiceStart {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue