[New platform] Introduce start phase for core services on server (#35297)
* introduce start phase. setup is bloated with start functionality
* fix amp typings: server is part of start contract now
* update mock files
* root.start(): necessary to run test server
* expose setup&start server api to simplify testing
* move tests to the new API
* test servers also should call root.start()
* update docs
* update snapshots: this functionality is tested in http server
* split setup/start phases
* update docs
* expose http server if it not started
to get rid of Optional<HttpServer> type and make it Require<HttpServer>
* adopt test to exposed Http server via SetupContract
* udpate docs
* cleanup apm changees
* check legacy service setup before start
* check http server setup before start
* restrict server options mutation; unify Promise interface for setup
* introduce start pahse for plugins service for parity with client side
* Revert "introduce start pahse for plugins service for parity with client side"
This reverts commit c04fdd2e26
.
This commit is contained in:
parent
2cc0b21566
commit
39091e7037
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md)
|
||||
|
||||
## AuthenticationHandler type
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [AuthToolkit](./kibana-plugin-server.authtoolkit.md) > [authenticated](./kibana-plugin-server.authtoolkit.authenticated.md)
|
||||
|
||||
## AuthToolkit.authenticated property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [AuthToolkit](./kibana-plugin-server.authtoolkit.md)
|
||||
|
||||
## AuthToolkit interface
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [AuthToolkit](./kibana-plugin-server.authtoolkit.md) > [redirected](./kibana-plugin-server.authtoolkit.redirected.md)
|
||||
|
||||
## AuthToolkit.redirected property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [AuthToolkit](./kibana-plugin-server.authtoolkit.md) > [rejected](./kibana-plugin-server.authtoolkit.rejected.md)
|
||||
|
||||
## AuthToolkit.rejected property
|
||||
|
|
|
@ -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) > [CoreStart](./kibana-plugin-server.corestart.md) > [http](./kibana-plugin-server.corestart.http.md)
|
||||
|
||||
## CoreStart.http property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
http: HttpServiceStart;
|
||||
```
|
|
@ -0,0 +1,18 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreStart](./kibana-plugin-server.corestart.md)
|
||||
|
||||
## CoreStart interface
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface CoreStart
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [http](./kibana-plugin-server.corestart.http.md) | <code>HttpServiceStart</code> | |
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
<!-- 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)
|
||||
|
||||
## HttpServiceSetup type
|
||||
|
@ -6,5 +8,5 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type HttpServiceSetup = HttpServerInfo;
|
||||
export declare type HttpServiceSetup = HttpServerSetup;
|
||||
```
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) > [isListening](./kibana-plugin-server.httpservicestart.islistening.md)
|
||||
|
||||
## HttpServiceStart.isListening property
|
||||
|
||||
Indicates if http server is listening on a port
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
isListening: () => boolean;
|
||||
```
|
|
@ -0,0 +1,19 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceStart](./kibana-plugin-server.httpservicestart.md)
|
||||
|
||||
## HttpServiceStart interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface HttpServiceStart
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [isListening](./kibana-plugin-server.httpservicestart.islistening.md) | <code>() => boolean</code> | Indicates if http server is listening on a port |
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [body](./kibana-plugin-server.kibanarequest.body.md)
|
||||
|
||||
## KibanaRequest.body property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [from](./kibana-plugin-server.kibanarequest.from.md)
|
||||
|
||||
## KibanaRequest.from() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [getFilteredHeaders](./kibana-plugin-server.kibanarequest.getfilteredheaders.md)
|
||||
|
||||
## KibanaRequest.getFilteredHeaders() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [headers](./kibana-plugin-server.kibanarequest.headers.md)
|
||||
|
||||
## KibanaRequest.headers property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md)
|
||||
|
||||
## KibanaRequest class
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [params](./kibana-plugin-server.kibanarequest.params.md)
|
||||
|
||||
## KibanaRequest.params property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [path](./kibana-plugin-server.kibanarequest.path.md)
|
||||
|
||||
## KibanaRequest.path property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [query](./kibana-plugin-server.kibanarequest.query.md)
|
||||
|
||||
## KibanaRequest.query property
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
| [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) | |
|
||||
| [CoreStart](./kibana-plugin-server.corestart.md) | |
|
||||
| [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | |
|
||||
| [HttpServiceStart](./kibana-plugin-server.httpservicestart.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 |
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestHandler](./kibana-plugin-server.onrequesthandler.md)
|
||||
|
||||
## OnRequestHandler type
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md)
|
||||
|
||||
## OnRequestToolkit interface
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [next](./kibana-plugin-server.onrequesttoolkit.next.md)
|
||||
|
||||
## OnRequestToolkit.next property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [redirected](./kibana-plugin-server.onrequesttoolkit.redirected.md)
|
||||
|
||||
## OnRequestToolkit.redirected property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [rejected](./kibana-plugin-server.onrequesttoolkit.rejected.md)
|
||||
|
||||
## OnRequestToolkit.rejected property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [PluginSetupContext](./kibana-plugin-server.pluginsetupcontext.md) > [http](./kibana-plugin-server.pluginsetupcontext.http.md)
|
||||
|
||||
## PluginSetupContext.http property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [delete](./kibana-plugin-server.router.delete.md)
|
||||
|
||||
## Router.delete() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [get](./kibana-plugin-server.router.get.md)
|
||||
|
||||
## Router.get() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [getRoutes](./kibana-plugin-server.router.getroutes.md)
|
||||
|
||||
## Router.getRoutes() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md)
|
||||
|
||||
## Router class
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [path](./kibana-plugin-server.router.path.md)
|
||||
|
||||
## Router.path property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [post](./kibana-plugin-server.router.post.md)
|
||||
|
||||
## Router.post() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [put](./kibana-plugin-server.router.put.md)
|
||||
|
||||
## Router.put() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [routes](./kibana-plugin-server.router.routes.md)
|
||||
|
||||
## Router.routes property
|
||||
|
|
|
@ -37,6 +37,9 @@ export type PluginsServiceStartDeps = CoreStart;
|
|||
export interface PluginsServiceSetup {
|
||||
contracts: Map<string, unknown>;
|
||||
}
|
||||
export interface PluginsServiceStart {
|
||||
contracts: Map<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service responsible for loading plugin bundles, initializing plugins, and managing the lifecycle
|
||||
|
@ -44,7 +47,7 @@ export interface PluginsServiceSetup {
|
|||
*
|
||||
* @internal
|
||||
*/
|
||||
export class PluginsService implements CoreService<PluginsServiceSetup> {
|
||||
export class PluginsService implements CoreService<PluginsServiceSetup, PluginsServiceStart> {
|
||||
/** Plugin wrappers in topological order. */
|
||||
private readonly plugins: Map<
|
||||
PluginName,
|
||||
|
|
|
@ -84,6 +84,7 @@ export async function bootstrap({
|
|||
|
||||
try {
|
||||
await root.setup();
|
||||
await root.start();
|
||||
} catch (err) {
|
||||
await shutdown(err);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ type ElasticsearchServiceContract = PublicMethodsOf<ElasticsearchService>;
|
|||
const createMock = () => {
|
||||
const mocked: jest.Mocked<ElasticsearchServiceContract> = {
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
mocked.setup.mockResolvedValue(createSetupContractMock());
|
||||
|
|
|
@ -106,6 +106,8 @@ export class ElasticsearchService implements CoreService<ElasticsearchServiceSet
|
|||
};
|
||||
}
|
||||
|
||||
public async start() {}
|
||||
|
||||
public async stop() {
|
||||
this.log.debug('Stopping elasticsearch service');
|
||||
|
||||
|
|
|
@ -1,49 +1,9 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`logs error if already set up 1`] = `
|
||||
Object {
|
||||
"debug": Array [],
|
||||
"error": Array [],
|
||||
"fatal": Array [],
|
||||
"info": Array [],
|
||||
"log": Array [],
|
||||
"trace": Array [],
|
||||
"warn": Array [
|
||||
Array [
|
||||
"Received new HTTP config after server was started. Config will **not** be applied.",
|
||||
],
|
||||
Array [
|
||||
Array [
|
||||
"Received new HTTP config after server was started. Config will **not** be applied.",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`register route handler 1`] = `
|
||||
Object {
|
||||
"debug": Array [
|
||||
Array [
|
||||
"registering route handler for [/foo]",
|
||||
],
|
||||
],
|
||||
"error": Array [],
|
||||
"fatal": Array [],
|
||||
"info": Array [],
|
||||
"log": Array [],
|
||||
"trace": Array [],
|
||||
"warn": Array [],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`throws if registering route handler after http server is set up 1`] = `
|
||||
Object {
|
||||
"debug": Array [],
|
||||
"error": Array [
|
||||
Array [
|
||||
"Received new router [/foo] after server was started. Router will **not** be applied.",
|
||||
],
|
||||
],
|
||||
"fatal": Array [],
|
||||
"info": Array [],
|
||||
"log": Array [],
|
||||
"trace": Array [],
|
||||
"warn": Array [],
|
||||
}
|
||||
]
|
||||
`;
|
||||
|
|
|
@ -83,7 +83,7 @@ const createHttpSchema = schema.object(
|
|||
}
|
||||
);
|
||||
|
||||
type HttpConfigType = TypeOf<typeof createHttpSchema>;
|
||||
export type HttpConfigType = TypeOf<typeof createHttpSchema>;
|
||||
|
||||
export class HttpConfig {
|
||||
/**
|
||||
|
|
|
@ -56,6 +56,7 @@ afterEach(async () => {
|
|||
test('listening after started', async () => {
|
||||
expect(server.isListening()).toBe(false);
|
||||
|
||||
await server.setup(config);
|
||||
await server.start(config);
|
||||
|
||||
expect(server.isListening()).toBe(true);
|
||||
|
@ -68,9 +69,9 @@ test('200 OK with body', async () => {
|
|||
return res.ok({ key: 'value' });
|
||||
});
|
||||
|
||||
server.registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get('/foo/')
|
||||
|
@ -87,9 +88,10 @@ test('202 Accepted with body', async () => {
|
|||
return res.accepted({ location: 'somewhere' });
|
||||
});
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get('/foo/')
|
||||
|
@ -106,9 +108,10 @@ test('204 No content', async () => {
|
|||
return res.noContent();
|
||||
});
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get('/foo/')
|
||||
|
@ -127,9 +130,10 @@ test('400 Bad request with error', async () => {
|
|||
return res.badRequest(err);
|
||||
});
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get('/foo/')
|
||||
|
@ -156,9 +160,10 @@ test('valid params', async () => {
|
|||
}
|
||||
);
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get('/foo/some-string')
|
||||
|
@ -185,9 +190,10 @@ test('invalid params', async () => {
|
|||
}
|
||||
);
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get('/foo/some-string')
|
||||
|
@ -217,9 +223,10 @@ test('valid query', async () => {
|
|||
}
|
||||
);
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get('/foo/?bar=test&quux=123')
|
||||
|
@ -246,9 +253,10 @@ test('invalid query', async () => {
|
|||
}
|
||||
);
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get('/foo/?bar=test')
|
||||
|
@ -278,9 +286,10 @@ test('valid body', async () => {
|
|||
}
|
||||
);
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.post('/foo/')
|
||||
|
@ -311,9 +320,10 @@ test('invalid body', async () => {
|
|||
}
|
||||
);
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.post('/foo/')
|
||||
|
@ -343,9 +353,10 @@ test('handles putting', async () => {
|
|||
}
|
||||
);
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.put('/foo/')
|
||||
|
@ -373,9 +384,10 @@ test('handles deleting', async () => {
|
|||
}
|
||||
);
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.delete('/foo/3')
|
||||
|
@ -398,9 +410,10 @@ test('filtered headers', async () => {
|
|||
return res.noContent();
|
||||
});
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(config);
|
||||
await server.start(config);
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get('/foo/?bar=quux')
|
||||
|
@ -430,9 +443,10 @@ describe('with `basepath: /bar` and `rewriteBasePath: false`', () => {
|
|||
res.ok({ key: 'value:/foo' })
|
||||
);
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(configWithBasePath);
|
||||
await server.start(configWithBasePath);
|
||||
innerServerListener = innerServer.listener;
|
||||
});
|
||||
|
||||
|
@ -490,9 +504,10 @@ describe('with `basepath: /bar` and `rewriteBasePath: true`', () => {
|
|||
res.ok({ key: 'value:/foo' })
|
||||
);
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter, server: innerServer } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
const { server: innerServer } = await server.start(configWithBasePath);
|
||||
await server.start(configWithBasePath);
|
||||
innerServerListener = innerServer.listener;
|
||||
});
|
||||
|
||||
|
@ -555,17 +570,19 @@ describe('with defined `redirectHttpFromPort`', () => {
|
|||
const router = new Router('/');
|
||||
router.get({ path: '/', validate: false }, async (req, res) => res.ok({ key: 'value:/' }));
|
||||
|
||||
server.registerRouter(router);
|
||||
const { registerRouter } = await server.setup(config);
|
||||
registerRouter(router);
|
||||
|
||||
await server.start(configWithSSL);
|
||||
});
|
||||
});
|
||||
|
||||
test('returns server and connection options on start', async () => {
|
||||
const { server: innerServer, options } = await server.start({
|
||||
const configWithPort = {
|
||||
...config,
|
||||
port: 12345,
|
||||
});
|
||||
};
|
||||
const { options, server: innerServer } = await server.setup(configWithPort);
|
||||
|
||||
expect(innerServer).toBeDefined();
|
||||
expect(innerServer).toBe((server as any).server);
|
||||
|
@ -573,7 +590,7 @@ test('returns server and connection options on start', async () => {
|
|||
});
|
||||
|
||||
test('registers auth request interceptor only once', async () => {
|
||||
const { registerAuth } = await server.start(config);
|
||||
const { registerAuth } = await server.setup(config);
|
||||
const doRegister = () =>
|
||||
registerAuth(() => null as any, {
|
||||
encryptionKey: 'any_password',
|
||||
|
@ -584,9 +601,15 @@ test('registers auth request interceptor only once', async () => {
|
|||
});
|
||||
|
||||
test('registers onRequest interceptor several times', async () => {
|
||||
const { registerOnRequest } = await server.start(config);
|
||||
const { registerOnRequest } = await server.setup(config);
|
||||
const doRegister = () => registerOnRequest(() => null as any);
|
||||
|
||||
doRegister();
|
||||
expect(doRegister).not.toThrowError();
|
||||
});
|
||||
|
||||
test('throws an error if starts without set up', async () => {
|
||||
await expect(server.start(config)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Http server is not setup up yet"`
|
||||
);
|
||||
});
|
||||
|
|
|
@ -26,14 +26,17 @@ import { createServer, getServerOptions } from './http_tools';
|
|||
import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth';
|
||||
import { adoptToHapiOnRequestFormat, OnRequestHandler } from './lifecycle/on_request';
|
||||
import { Router } from './router';
|
||||
import { deepFreeze, RecursiveReadonly } from './lib/deep_freeze';
|
||||
|
||||
import {
|
||||
SessionStorageCookieOptions,
|
||||
createCookieSessionStorageFactory,
|
||||
} from './cookie_session_storage';
|
||||
|
||||
export interface HttpServerInfo {
|
||||
export interface HttpServerSetup {
|
||||
server: Server;
|
||||
options: ServerOptions;
|
||||
options: RecursiveReadonly<ServerOptions>;
|
||||
registerRouter: (router: Router) => void;
|
||||
/**
|
||||
* Define custom authentication and/or authorization mechanism for incoming requests.
|
||||
* Applied to all resources by default. Only one AuthenticationHandler can be registered.
|
||||
|
@ -61,20 +64,40 @@ export class HttpServer {
|
|||
return this.server !== undefined && this.server.listener.listening;
|
||||
}
|
||||
|
||||
public registerRouter(router: Router) {
|
||||
private registerRouter(router: Router) {
|
||||
if (this.isListening()) {
|
||||
throw new Error('Routers can be registered only when HTTP server is stopped.');
|
||||
}
|
||||
|
||||
this.log.debug(`registering route handler for [${router.path}]`);
|
||||
this.registeredRouters.add(router);
|
||||
}
|
||||
|
||||
public async start(config: HttpConfig): Promise<HttpServerInfo> {
|
||||
this.log.debug('starting http server');
|
||||
|
||||
public setup(config: HttpConfig): HttpServerSetup {
|
||||
const serverOptions = getServerOptions(config);
|
||||
this.server = createServer(serverOptions);
|
||||
|
||||
return {
|
||||
options: deepFreeze(serverOptions),
|
||||
registerRouter: this.registerRouter.bind(this),
|
||||
registerOnRequest: this.registerOnRequest.bind(this),
|
||||
registerAuth: <T>(
|
||||
fn: AuthenticationHandler<T>,
|
||||
cookieOptions: SessionStorageCookieOptions<T>
|
||||
) => this.registerAuth(fn, cookieOptions, config.basePath),
|
||||
// Return server instance with the connection options so that we can properly
|
||||
// bridge core and the "legacy" Kibana internally. Once this bridge isn't
|
||||
// needed anymore we shouldn't return the instance from this method.
|
||||
server: this.server,
|
||||
};
|
||||
}
|
||||
|
||||
public async start(config: HttpConfig) {
|
||||
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);
|
||||
|
||||
for (const router of this.registeredRouters) {
|
||||
|
@ -94,19 +117,6 @@ export class HttpServer {
|
|||
config.rewriteBasePath ? config.basePath : ''
|
||||
}`
|
||||
);
|
||||
|
||||
// Return server instance with the connection options so that we can properly
|
||||
// bridge core and the "legacy" Kibana internally. Once this bridge isn't
|
||||
// needed anymore we shouldn't return anything from this method.
|
||||
return {
|
||||
server: this.server,
|
||||
options: serverOptions,
|
||||
registerOnRequest: this.registerOnRequest.bind(this),
|
||||
registerAuth: <T>(
|
||||
fn: AuthenticationHandler<T>,
|
||||
cookieOptions: SessionStorageCookieOptions<T>
|
||||
) => this.registerAuth(fn, cookieOptions, config.basePath),
|
||||
};
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
|
|
|
@ -22,23 +22,33 @@ import { HttpService } from './http_service';
|
|||
|
||||
const createSetupContractMock = () => {
|
||||
const setupContract = {
|
||||
// we can mock some hapi server method when we need it
|
||||
server: {} as Server,
|
||||
options: {} as ServerOptions,
|
||||
registerAuth: jest.fn(),
|
||||
registerOnRequest: jest.fn(),
|
||||
registerRouter: jest.fn(),
|
||||
// we can mock some hapi server method when we need it
|
||||
server: {} as Server,
|
||||
};
|
||||
return setupContract;
|
||||
};
|
||||
|
||||
const createStartContractMock = () => {
|
||||
const startContract = {
|
||||
isListening: jest.fn(),
|
||||
};
|
||||
startContract.isListening.mockReturnValue(true);
|
||||
return startContract;
|
||||
};
|
||||
|
||||
type HttpServiceContract = PublicMethodsOf<HttpService>;
|
||||
const createHttpServiceMock = () => {
|
||||
const mocked: jest.Mocked<HttpServiceContract> = {
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
registerRouter: jest.fn(),
|
||||
};
|
||||
mocked.setup.mockResolvedValue(createSetupContractMock());
|
||||
mocked.start.mockResolvedValue(createStartContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
|
|
|
@ -21,75 +21,91 @@ import { mockHttpServer } from './http_service.test.mocks';
|
|||
|
||||
import { noop } from 'lodash';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { HttpConfig, HttpService, Router } from '.';
|
||||
import { HttpService, Router } from '.';
|
||||
import { HttpConfigType } from './http_config';
|
||||
import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../config';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
import { getEnvOptions } from '../config/__mocks__/env';
|
||||
|
||||
const logger = loggingServiceMock.create();
|
||||
const env = Env.createDefault(getEnvOptions());
|
||||
|
||||
const createConfigService = (value: Partial<HttpConfigType> = {}) =>
|
||||
new ConfigService(
|
||||
new BehaviorSubject<Config>(
|
||||
new ObjectToConfigAdapter({
|
||||
http: value,
|
||||
})
|
||||
),
|
||||
env,
|
||||
logger
|
||||
);
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('creates and sets up http server', async () => {
|
||||
const config = {
|
||||
const configService = createConfigService({
|
||||
host: 'example.org',
|
||||
port: 1234,
|
||||
ssl: {},
|
||||
} as HttpConfig;
|
||||
|
||||
const config$ = new BehaviorSubject(config);
|
||||
});
|
||||
|
||||
const httpServer = {
|
||||
isListening: () => false,
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
stop: noop,
|
||||
};
|
||||
mockHttpServer.mockImplementation(() => httpServer);
|
||||
|
||||
const service = new HttpService(config$.asObservable(), logger);
|
||||
const service = new HttpService({ configService, env, logger });
|
||||
|
||||
expect(mockHttpServer.mock.instances.length).toBe(1);
|
||||
expect(httpServer.start).not.toHaveBeenCalled();
|
||||
|
||||
expect(httpServer.setup).not.toHaveBeenCalled();
|
||||
|
||||
await service.setup();
|
||||
expect(httpServer.setup).toHaveBeenCalledTimes(1);
|
||||
expect(httpServer.start).not.toHaveBeenCalled();
|
||||
|
||||
await service.start();
|
||||
expect(httpServer.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('logs error if already set up', async () => {
|
||||
const config = { ssl: {} } as HttpConfig;
|
||||
|
||||
const config$ = new BehaviorSubject(config);
|
||||
const configService = createConfigService();
|
||||
|
||||
const httpServer = {
|
||||
isListening: () => true,
|
||||
setup: jest.fn(),
|
||||
start: noop,
|
||||
stop: noop,
|
||||
};
|
||||
mockHttpServer.mockImplementation(() => httpServer);
|
||||
|
||||
const service = new HttpService(config$.asObservable(), logger);
|
||||
const service = new HttpService({ configService, env, logger });
|
||||
|
||||
await service.setup();
|
||||
|
||||
expect(loggingServiceMock.collect(logger)).toMatchSnapshot();
|
||||
expect(loggingServiceMock.collect(logger).warn).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('stops http server', async () => {
|
||||
const config = { ssl: {} } as HttpConfig;
|
||||
|
||||
const config$ = new BehaviorSubject(config);
|
||||
const configService = createConfigService();
|
||||
|
||||
const httpServer = {
|
||||
isListening: () => false,
|
||||
setup: noop,
|
||||
start: noop,
|
||||
stop: jest.fn(),
|
||||
};
|
||||
mockHttpServer.mockImplementation(() => httpServer);
|
||||
|
||||
const service = new HttpService(config$.asObservable(), logger);
|
||||
const service = new HttpService({ configService, env, logger });
|
||||
|
||||
await service.setup();
|
||||
await service.start();
|
||||
|
||||
expect(httpServer.stop).toHaveBeenCalledTimes(0);
|
||||
|
||||
|
@ -98,52 +114,30 @@ test('stops http server', async () => {
|
|||
expect(httpServer.stop).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('register route handler', () => {
|
||||
const config = {} as HttpConfig;
|
||||
|
||||
const config$ = new BehaviorSubject(config);
|
||||
test('register route handler', async () => {
|
||||
const configService = createConfigService({});
|
||||
|
||||
const registerRouterMock = jest.fn();
|
||||
const httpServer = {
|
||||
isListening: () => false,
|
||||
registerRouter: jest.fn(),
|
||||
setup: () => ({ registerRouter: registerRouterMock }),
|
||||
start: noop,
|
||||
stop: noop,
|
||||
};
|
||||
mockHttpServer.mockImplementation(() => httpServer);
|
||||
|
||||
const service = new HttpService(config$.asObservable(), logger);
|
||||
const service = new HttpService({ configService, env, logger });
|
||||
|
||||
const router = new Router('/foo');
|
||||
service.registerRouter(router);
|
||||
const { registerRouter } = await service.setup();
|
||||
registerRouter(router);
|
||||
|
||||
expect(httpServer.registerRouter).toHaveBeenCalledTimes(1);
|
||||
expect(httpServer.registerRouter).toHaveBeenLastCalledWith(router);
|
||||
expect(loggingServiceMock.collect(logger)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('throws if registering route handler after http server is set up', () => {
|
||||
const config = {} as HttpConfig;
|
||||
|
||||
const config$ = new BehaviorSubject(config);
|
||||
|
||||
const httpServer = {
|
||||
isListening: () => true,
|
||||
registerRouter: jest.fn(),
|
||||
start: noop,
|
||||
stop: noop,
|
||||
};
|
||||
mockHttpServer.mockImplementation(() => httpServer);
|
||||
|
||||
const service = new HttpService(config$.asObservable(), logger);
|
||||
|
||||
const router = new Router('/foo');
|
||||
service.registerRouter(router);
|
||||
|
||||
expect(httpServer.registerRouter).toHaveBeenCalledTimes(0);
|
||||
expect(loggingServiceMock.collect(logger)).toMatchSnapshot();
|
||||
expect(registerRouterMock).toHaveBeenCalledTimes(1);
|
||||
expect(registerRouterMock).toHaveBeenLastCalledWith(router);
|
||||
});
|
||||
|
||||
test('returns http server contract on setup', async () => {
|
||||
const configService = createConfigService();
|
||||
const httpServer = {
|
||||
server: {},
|
||||
options: { someOption: true },
|
||||
|
@ -151,11 +145,57 @@ test('returns http server contract on setup', async () => {
|
|||
|
||||
mockHttpServer.mockImplementation(() => ({
|
||||
isListening: () => false,
|
||||
start: jest.fn().mockReturnValue(httpServer),
|
||||
setup: jest.fn().mockReturnValue(httpServer),
|
||||
stop: noop,
|
||||
}));
|
||||
|
||||
const service = new HttpService(new BehaviorSubject({ ssl: {} } as HttpConfig), logger);
|
||||
const service = new HttpService({ configService, env, logger });
|
||||
|
||||
expect(await service.setup()).toBe(httpServer);
|
||||
});
|
||||
|
||||
test('does not start http server if process is dev cluster master', async () => {
|
||||
const configService = createConfigService({});
|
||||
const httpServer = {
|
||||
isListening: () => false,
|
||||
setup: noop,
|
||||
start: jest.fn(),
|
||||
stop: noop,
|
||||
};
|
||||
mockHttpServer.mockImplementation(() => httpServer);
|
||||
|
||||
const service = new HttpService({
|
||||
configService,
|
||||
env: new Env('.', getEnvOptions({ isDevClusterMaster: true })),
|
||||
logger,
|
||||
});
|
||||
|
||||
await service.setup();
|
||||
await service.start();
|
||||
|
||||
expect(httpServer.start).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('does not start http server if configured with `autoListen:false`', async () => {
|
||||
const configService = createConfigService({
|
||||
autoListen: false,
|
||||
});
|
||||
const httpServer = {
|
||||
isListening: () => false,
|
||||
setup: noop,
|
||||
start: jest.fn(),
|
||||
stop: noop,
|
||||
};
|
||||
mockHttpServer.mockImplementation(() => httpServer);
|
||||
|
||||
const service = new HttpService({
|
||||
configService,
|
||||
env: new Env('.', getEnvOptions({ isDevClusterMaster: true })),
|
||||
logger,
|
||||
});
|
||||
|
||||
await service.setup();
|
||||
await service.start();
|
||||
|
||||
expect(httpServer.start).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
|
@ -21,28 +21,37 @@ import { Observable, Subscription } from 'rxjs';
|
|||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { CoreService } from '../../types';
|
||||
import { Logger, LoggerFactory } from '../logging';
|
||||
import { Logger } from '../logging';
|
||||
import { CoreContext } from '../core_context';
|
||||
import { HttpConfig } from './http_config';
|
||||
import { HttpServer, HttpServerInfo } from './http_server';
|
||||
import { HttpServer, HttpServerSetup } from './http_server';
|
||||
import { HttpsRedirectServer } from './https_redirect_server';
|
||||
import { Router } from './router';
|
||||
|
||||
/** @public */
|
||||
export type HttpServiceSetup = HttpServerInfo;
|
||||
export type HttpServiceSetup = HttpServerSetup;
|
||||
/** @public */
|
||||
export interface HttpServiceStart {
|
||||
/** Indicates if http server is listening on a port */
|
||||
isListening: () => boolean;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export class HttpService implements CoreService<HttpServiceSetup> {
|
||||
export class HttpService implements CoreService<HttpServiceSetup, HttpServiceStart> {
|
||||
private readonly httpServer: HttpServer;
|
||||
private readonly httpsRedirectServer: HttpsRedirectServer;
|
||||
private readonly config$: Observable<HttpConfig>;
|
||||
private configSubscription?: Subscription;
|
||||
|
||||
private readonly log: Logger;
|
||||
|
||||
constructor(private readonly config$: Observable<HttpConfig>, logger: LoggerFactory) {
|
||||
this.log = logger.get('http');
|
||||
constructor(private readonly coreContext: CoreContext) {
|
||||
this.log = coreContext.logger.get('http');
|
||||
this.config$ = this.coreContext.configService.atPath('server', HttpConfig);
|
||||
|
||||
this.httpServer = new HttpServer(logger.get('http', 'server'));
|
||||
this.httpsRedirectServer = new HttpsRedirectServer(logger.get('http', 'redirect', 'server'));
|
||||
this.httpServer = new HttpServer(coreContext.logger.get('http', 'server'));
|
||||
this.httpsRedirectServer = new HttpsRedirectServer(
|
||||
coreContext.logger.get('http', 'redirect', 'server')
|
||||
);
|
||||
}
|
||||
|
||||
public async setup() {
|
||||
|
@ -58,16 +67,29 @@ export class HttpService implements CoreService<HttpServiceSetup> {
|
|||
|
||||
const config = await this.config$.pipe(first()).toPromise();
|
||||
|
||||
// If a redirect port is specified, we start an HTTP server at this port and
|
||||
// redirect all requests to the SSL port.
|
||||
if (config.ssl.enabled && config.ssl.redirectHttpFromPort !== undefined) {
|
||||
await this.httpsRedirectServer.start(config);
|
||||
return this.httpServer.setup(config);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
const config = await this.config$.pipe(first()).toPromise();
|
||||
|
||||
// We shouldn't set up http service in two cases:`
|
||||
// 1. If `server.autoListen` is explicitly set to `false`.
|
||||
// 2. When the process is run as dev cluster master in which case cluster manager
|
||||
// will fork a dedicated process where http service will be set up instead.
|
||||
if (!this.coreContext.env.isDevClusterMaster && config.autoListen) {
|
||||
// If a redirect port is specified, we start an HTTP server at this port and
|
||||
// redirect all requests to the SSL port.
|
||||
if (config.ssl.enabled && config.ssl.redirectHttpFromPort !== undefined) {
|
||||
await this.httpsRedirectServer.start(config);
|
||||
}
|
||||
|
||||
await this.httpServer.start(config);
|
||||
}
|
||||
|
||||
// The HttpService's setup method calls `start` on HttpServer because it is
|
||||
// a more appropriate name. In the future, starting the server should be moved
|
||||
// to the `start` lifecycle handler of HttpService.
|
||||
return await this.httpServer.start(config);
|
||||
return {
|
||||
isListening: () => this.httpServer.isListening(),
|
||||
};
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
|
@ -81,19 +103,4 @@ export class HttpService implements CoreService<HttpServiceSetup> {
|
|||
await this.httpServer.stop();
|
||||
await this.httpsRedirectServer.stop();
|
||||
}
|
||||
|
||||
public registerRouter(router: Router): void {
|
||||
if (this.httpServer.isListening()) {
|
||||
// If the server is already running we can't make any config changes
|
||||
// to it, so we warn and don't allow the config to pass through.
|
||||
// TODO Should we throw instead?
|
||||
this.log.error(
|
||||
`Received new router [${router.path}] after server was started. ` +
|
||||
'Router will **not** be applied.'
|
||||
);
|
||||
} else {
|
||||
this.log.debug(`registering route handler for [${router.path}]`);
|
||||
this.httpServer.registerRouter(router);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,8 @@
|
|||
*/
|
||||
|
||||
export { HttpConfig } from './http_config';
|
||||
export { HttpService, HttpServiceSetup } from './http_service';
|
||||
export { HttpService, HttpServiceSetup, HttpServiceStart } from './http_service';
|
||||
export { Router, KibanaRequest } from './router';
|
||||
export { HttpServerInfo } from './http_server';
|
||||
export { BasePathProxyServer } from './base_path_proxy_server';
|
||||
export { AuthenticationHandler, AuthToolkit } from './lifecycle/auth';
|
||||
export { OnRequestHandler, OnRequestToolkit } from './lifecycle/on_request';
|
||||
|
|
|
@ -43,10 +43,10 @@ describe('http service', () => {
|
|||
router.get({ path: authUrl.auth, validate: false }, async (req, res) =>
|
||||
res.ok({ content: 'ok' })
|
||||
);
|
||||
// TODO fix me when registerRouter is available before HTTP server is run
|
||||
(root as any).server.http.registerRouter(router);
|
||||
|
||||
await root.setup();
|
||||
const { http } = await root.setup();
|
||||
http.registerRouter(router);
|
||||
await root.start();
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => await root.shutdown());
|
||||
|
@ -129,10 +129,11 @@ describe('http service', () => {
|
|||
[onReqUrl.root, onReqUrl.independentReq].forEach(url =>
|
||||
router.get({ path: url, validate: false }, async (req, res) => res.ok({ content: 'ok' }))
|
||||
);
|
||||
// TODO fix me when registerRouter is available before HTTP server is run
|
||||
(root as any).server.http.registerRouter(router);
|
||||
|
||||
await root.setup();
|
||||
const { http } = await root.setup();
|
||||
http.registerRouter(router);
|
||||
|
||||
await root.start();
|
||||
}, 30000);
|
||||
|
||||
afterAll(async () => await root.shutdown());
|
||||
|
|
42
src/core/server/http/lib/deep_freeze.ts
Normal file
42
src/core/server/http/lib/deep_freeze.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you 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.
|
||||
*/
|
||||
|
||||
type Freezable = { [k: string]: any } | any[];
|
||||
|
||||
// if we define this inside RecursiveReadonly TypeScript complains
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface RecursiveReadonlyArray<T> extends Array<RecursiveReadonly<T>> {}
|
||||
|
||||
export type RecursiveReadonly<T> = T extends any[]
|
||||
? RecursiveReadonlyArray<T[number]>
|
||||
: T extends object
|
||||
? Readonly<{ [K in keyof T]: RecursiveReadonly<T[K]> }>
|
||||
: T;
|
||||
|
||||
export function deepFreeze<T extends Freezable>(object: T) {
|
||||
// for any properties that reference an object, makes sure that object is
|
||||
// recursively frozen as well
|
||||
for (const value of Object.values(object)) {
|
||||
if (value !== null && typeof value === 'object') {
|
||||
deepFreeze(value);
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze(object) as RecursiveReadonly<T>;
|
||||
}
|
|
@ -23,7 +23,7 @@ jest.doMock('./http/http_service', () => ({
|
|||
HttpService: jest.fn(() => httpService),
|
||||
}));
|
||||
|
||||
export const mockPluginsService = { setup: jest.fn(), stop: jest.fn() };
|
||||
export const mockPluginsService = { setup: jest.fn(), start: jest.fn(), stop: jest.fn() };
|
||||
jest.doMock('./plugins/plugins_service', () => ({
|
||||
PluginsService: jest.fn(() => mockPluginsService),
|
||||
}));
|
||||
|
@ -34,7 +34,7 @@ jest.doMock('./elasticsearch/elasticsearch_service', () => ({
|
|||
ElasticsearchService: jest.fn(() => elasticsearchService),
|
||||
}));
|
||||
|
||||
export const mockLegacyService = { setup: jest.fn(), stop: jest.fn() };
|
||||
export const mockLegacyService = { setup: jest.fn(), start: jest.fn(), stop: jest.fn() };
|
||||
jest.mock('./legacy/legacy_service', () => ({
|
||||
LegacyService: jest.fn(() => mockLegacyService),
|
||||
}));
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { ElasticsearchServiceSetup } from './elasticsearch';
|
||||
import { HttpServiceSetup } from './http';
|
||||
import { HttpServiceSetup, HttpServiceStart } from './http';
|
||||
import { PluginsServiceSetup } from './plugins';
|
||||
|
||||
export { bootstrap } from './bootstrap';
|
||||
|
@ -56,4 +56,8 @@ export interface CoreSetup {
|
|||
plugins: PluginsServiceSetup;
|
||||
}
|
||||
|
||||
export { HttpServiceSetup, ElasticsearchServiceSetup, PluginsServiceSetup };
|
||||
export interface CoreStart {
|
||||
http: HttpServiceStart;
|
||||
}
|
||||
|
||||
export { HttpServiceSetup, HttpServiceStart, ElasticsearchServiceSetup, PluginsServiceSetup };
|
||||
|
|
|
@ -36,7 +36,7 @@ Array [
|
|||
"debug": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
"setting up legacy service",
|
||||
"starting legacy service",
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
|
|
|
@ -32,6 +32,7 @@ import { Config, Env, ObjectToConfigAdapter } from '../config';
|
|||
import { getEnvOptions } from '../config/__mocks__/env';
|
||||
import { configServiceMock } from '../config/config_service.mock';
|
||||
import { ElasticsearchServiceSetup } from '../elasticsearch';
|
||||
import { HttpServiceStart } from '../http';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
import { DiscoveredPlugin, DiscoveredPluginInternal } from '../plugins';
|
||||
import { PluginsServiceSetup } from '../plugins/plugins_service';
|
||||
|
@ -48,6 +49,11 @@ let setupDeps: {
|
|||
http: any;
|
||||
plugins: PluginsServiceSetup;
|
||||
};
|
||||
|
||||
let startDeps: {
|
||||
http: HttpServiceStart;
|
||||
};
|
||||
|
||||
const logger = loggingServiceMock.create();
|
||||
let configService: ReturnType<typeof configServiceMock.create>;
|
||||
|
||||
|
@ -60,8 +66,8 @@ beforeEach(() => {
|
|||
setupDeps = {
|
||||
elasticsearch: { legacy: {} } as any,
|
||||
http: {
|
||||
server: { listener: { addListener: jest.fn() }, route: jest.fn() },
|
||||
options: { someOption: 'foo', someAnotherOption: 'bar' },
|
||||
server: { listener: { addListener: jest.fn() }, route: jest.fn() },
|
||||
},
|
||||
plugins: {
|
||||
contracts: new Map([['plugin-id', 'plugin-value']]),
|
||||
|
@ -72,6 +78,12 @@ beforeEach(() => {
|
|||
},
|
||||
};
|
||||
|
||||
startDeps = {
|
||||
http: {
|
||||
isListening: () => true,
|
||||
},
|
||||
};
|
||||
|
||||
config$ = new BehaviorSubject<Config>(
|
||||
new ObjectToConfigAdapter({
|
||||
elasticsearch: { hosts: ['http://127.0.0.1'] },
|
||||
|
@ -92,6 +104,7 @@ afterEach(() => {
|
|||
describe('once LegacyService is set up with connection info', () => {
|
||||
test('register proxy route.', async () => {
|
||||
await legacyService.setup(setupDeps);
|
||||
await legacyService.start(startDeps);
|
||||
|
||||
expect(setupDeps.http.server.route.mock.calls).toMatchSnapshot('proxy route options');
|
||||
});
|
||||
|
@ -107,7 +120,8 @@ describe('once LegacyService is set up with connection info', () => {
|
|||
|
||||
// Wait until listen is called and proxy route is registered, but don't allow
|
||||
// listen to complete and make kbnServer available.
|
||||
const legacySetupPromise = legacyService.setup(setupDeps);
|
||||
await legacyService.setup(setupDeps);
|
||||
const legacySetupPromise = legacyService.start(startDeps);
|
||||
await kbnServerListen$.pipe(first()).toPromise();
|
||||
|
||||
const mockResponse: any = {
|
||||
|
@ -156,20 +170,20 @@ describe('once LegacyService is set up with connection info', () => {
|
|||
configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
|
||||
|
||||
await legacyService.setup(setupDeps);
|
||||
await legacyService.start(startDeps);
|
||||
|
||||
expect(MockKbnServer).toHaveBeenCalledTimes(1);
|
||||
expect(MockKbnServer).toHaveBeenCalledWith(
|
||||
{ server: { autoListen: true } },
|
||||
{
|
||||
elasticsearch: setupDeps.elasticsearch,
|
||||
http: setupDeps.http,
|
||||
setupDeps,
|
||||
startDeps,
|
||||
serverOptions: {
|
||||
listener: expect.any(LegacyPlatformProxy),
|
||||
someAnotherOption: 'bar',
|
||||
someOption: 'foo',
|
||||
},
|
||||
handledConfigPaths: ['foo.bar'],
|
||||
plugins: setupDeps.plugins,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -182,20 +196,20 @@ describe('once LegacyService is set up with connection info', () => {
|
|||
configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: false }));
|
||||
|
||||
await legacyService.setup(setupDeps);
|
||||
await legacyService.start(startDeps);
|
||||
|
||||
expect(MockKbnServer).toHaveBeenCalledTimes(1);
|
||||
expect(MockKbnServer).toHaveBeenCalledWith(
|
||||
{ server: { autoListen: true } },
|
||||
{
|
||||
elasticsearch: setupDeps.elasticsearch,
|
||||
http: setupDeps.http,
|
||||
setupDeps,
|
||||
startDeps,
|
||||
serverOptions: {
|
||||
listener: expect.any(LegacyPlatformProxy),
|
||||
someAnotherOption: 'bar',
|
||||
someOption: 'foo',
|
||||
},
|
||||
handledConfigPaths: ['foo.bar'],
|
||||
plugins: setupDeps.plugins,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -209,7 +223,8 @@ describe('once LegacyService is set up with connection info', () => {
|
|||
configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
|
||||
MockKbnServer.prototype.listen.mockRejectedValue(new Error('something failed'));
|
||||
|
||||
await expect(legacyService.setup(setupDeps)).rejects.toThrowErrorMatchingSnapshot();
|
||||
await legacyService.setup(setupDeps);
|
||||
await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingSnapshot();
|
||||
|
||||
const [mockKbnServer] = MockKbnServer.mock.instances;
|
||||
expect(mockKbnServer.listen).toHaveBeenCalled();
|
||||
|
@ -219,7 +234,8 @@ describe('once LegacyService is set up with connection info', () => {
|
|||
test('throws if fails to retrieve initial config.', async () => {
|
||||
configService.getConfig$.mockReturnValue(throwError(new Error('something failed')));
|
||||
|
||||
await expect(legacyService.setup(setupDeps)).rejects.toThrowErrorMatchingSnapshot();
|
||||
await legacyService.setup(setupDeps);
|
||||
await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingSnapshot();
|
||||
|
||||
expect(MockKbnServer).not.toHaveBeenCalled();
|
||||
expect(MockClusterManager).not.toHaveBeenCalled();
|
||||
|
@ -227,6 +243,7 @@ describe('once LegacyService is set up with connection info', () => {
|
|||
|
||||
test('reconfigures logging configuration if new config is received.', async () => {
|
||||
await legacyService.setup(setupDeps);
|
||||
await legacyService.start(startDeps);
|
||||
|
||||
const [mockKbnServer] = MockKbnServer.mock.instances as Array<jest.Mocked<KbnServer>>;
|
||||
expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled();
|
||||
|
@ -240,6 +257,7 @@ describe('once LegacyService is set up with connection info', () => {
|
|||
|
||||
test('logs error if re-configuring fails.', async () => {
|
||||
await legacyService.setup(setupDeps);
|
||||
await legacyService.start(startDeps);
|
||||
|
||||
const [mockKbnServer] = MockKbnServer.mock.instances as Array<jest.Mocked<KbnServer>>;
|
||||
expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled();
|
||||
|
@ -257,6 +275,7 @@ describe('once LegacyService is set up with connection info', () => {
|
|||
|
||||
test('logs error if config service fails.', async () => {
|
||||
await legacyService.setup(setupDeps);
|
||||
await legacyService.start(startDeps);
|
||||
|
||||
const [mockKbnServer] = MockKbnServer.mock.instances;
|
||||
expect(mockKbnServer.applyLoggingConfiguration).not.toHaveBeenCalled();
|
||||
|
@ -274,6 +293,7 @@ describe('once LegacyService is set up with connection info', () => {
|
|||
const mockRequest = { raw: { req: { a: 1 }, res: { b: 2 } } };
|
||||
|
||||
await legacyService.setup(setupDeps);
|
||||
await legacyService.start(startDeps);
|
||||
|
||||
const [[{ handler }]] = setupDeps.http.server.route.mock.calls;
|
||||
const response = await handler(mockRequest, mockResponseToolkit);
|
||||
|
@ -293,11 +313,14 @@ describe('once LegacyService is set up with connection info', () => {
|
|||
});
|
||||
|
||||
describe('once LegacyService is set up without connection info', () => {
|
||||
const disabledHttpStartDeps = {
|
||||
http: {
|
||||
isListening: () => false,
|
||||
},
|
||||
};
|
||||
beforeEach(async () => {
|
||||
await legacyService.setup({
|
||||
elasticsearch: setupDeps.elasticsearch,
|
||||
plugins: setupDeps.plugins,
|
||||
});
|
||||
await legacyService.setup(setupDeps);
|
||||
await legacyService.start(disabledHttpStartDeps);
|
||||
});
|
||||
|
||||
test('creates legacy kbnServer with `autoListen: false`.', () => {
|
||||
|
@ -306,10 +329,10 @@ describe('once LegacyService is set up without connection info', () => {
|
|||
expect(MockKbnServer).toHaveBeenCalledWith(
|
||||
{ server: { autoListen: true } },
|
||||
{
|
||||
elasticsearch: setupDeps.elasticsearch,
|
||||
setupDeps,
|
||||
startDeps: disabledHttpStartDeps,
|
||||
serverOptions: { autoListen: false },
|
||||
handledConfigPaths: ['foo.bar'],
|
||||
plugins: setupDeps.plugins,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -350,6 +373,12 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => {
|
|||
await devClusterLegacyService.setup({
|
||||
elasticsearch: setupDeps.elasticsearch,
|
||||
plugins: { contracts: new Map(), uiPlugins: { public: new Map(), internal: new Map() } },
|
||||
http: setupDeps.http,
|
||||
});
|
||||
await devClusterLegacyService.start({
|
||||
http: {
|
||||
isListening: () => false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(MockClusterManager.create.mock.calls).toMatchSnapshot(
|
||||
|
@ -372,6 +401,12 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => {
|
|||
await devClusterLegacyService.setup({
|
||||
elasticsearch: setupDeps.elasticsearch,
|
||||
plugins: { contracts: new Map(), uiPlugins: { public: new Map(), internal: new Map() } },
|
||||
http: setupDeps.http,
|
||||
});
|
||||
await devClusterLegacyService.start({
|
||||
http: {
|
||||
isListening: () => false,
|
||||
},
|
||||
});
|
||||
|
||||
expect(MockClusterManager.create.mock.calls).toMatchSnapshot(
|
||||
|
@ -379,3 +414,9 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('Cannot start without setup phase', async () => {
|
||||
await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Legacy service is not setup yet."`
|
||||
);
|
||||
});
|
||||
|
|
|
@ -21,13 +21,12 @@ import { Server as HapiServer } from 'hapi';
|
|||
import { combineLatest, ConnectableObservable, EMPTY, Subscription } from 'rxjs';
|
||||
import { first, map, mergeMap, publishReplay, tap } from 'rxjs/operators';
|
||||
import { CoreService } from '../../types';
|
||||
import { CoreSetup, CoreStart } from '../../server';
|
||||
import { Config } from '../config';
|
||||
import { CoreContext } from '../core_context';
|
||||
import { DevConfig } from '../dev';
|
||||
import { ElasticsearchServiceSetup } from '../elasticsearch';
|
||||
import { BasePathProxyServer, HttpConfig, HttpServiceSetup } from '../http';
|
||||
import { BasePathProxyServer, HttpConfig } from '../http';
|
||||
import { Logger } from '../logging';
|
||||
import { PluginsServiceSetup } from '../plugins/plugins_service';
|
||||
import { LegacyPlatformProxy } from './legacy_platform_proxy';
|
||||
|
||||
interface LegacyKbnServer {
|
||||
|
@ -37,12 +36,6 @@ interface LegacyKbnServer {
|
|||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface SetupDeps {
|
||||
elasticsearch: ElasticsearchServiceSetup;
|
||||
http?: HttpServiceSetup;
|
||||
plugins: PluginsServiceSetup;
|
||||
}
|
||||
|
||||
function getLegacyRawConfig(config: Config) {
|
||||
const rawConfig = config.toRaw();
|
||||
|
||||
|
@ -60,13 +53,20 @@ export class LegacyService implements CoreService {
|
|||
private readonly log: Logger;
|
||||
private kbnServer?: LegacyKbnServer;
|
||||
private configSubscription?: Subscription;
|
||||
private setupDeps?: CoreSetup;
|
||||
|
||||
constructor(private readonly coreContext: CoreContext) {
|
||||
this.log = coreContext.logger.get('legacy-service');
|
||||
}
|
||||
|
||||
public async setup(deps: SetupDeps) {
|
||||
this.log.debug('setting up legacy service');
|
||||
public async setup(setupDeps: CoreSetup) {
|
||||
this.setupDeps = setupDeps;
|
||||
}
|
||||
public async start(startDeps: CoreStart) {
|
||||
const { setupDeps } = this;
|
||||
if (!setupDeps) {
|
||||
throw new Error('Legacy service is not setup yet.');
|
||||
}
|
||||
this.log.debug('starting legacy service');
|
||||
|
||||
const update$ = this.coreContext.configService.getConfig$().pipe(
|
||||
tap(config => {
|
||||
|
@ -89,7 +89,7 @@ export class LegacyService implements CoreService {
|
|||
await this.createClusterManager(config);
|
||||
return;
|
||||
}
|
||||
return await this.createKbnServer(config, deps);
|
||||
return await this.createKbnServer(config, setupDeps, startDeps);
|
||||
})
|
||||
)
|
||||
.toPromise();
|
||||
|
@ -130,7 +130,7 @@ export class LegacyService implements CoreService {
|
|||
);
|
||||
}
|
||||
|
||||
private async createKbnServer(config: Config, { elasticsearch, http, plugins }: SetupDeps) {
|
||||
private async createKbnServer(config: Config, setupDeps: CoreSetup, startDeps: CoreStart) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const KbnServer = require('../../../legacy/server/kbn_server');
|
||||
const kbnServer: LegacyKbnServer = new KbnServer(getLegacyRawConfig(config), {
|
||||
|
@ -139,17 +139,15 @@ export class LegacyService implements CoreService {
|
|||
// bridge with the "legacy" Kibana. If server isn't run (e.g. if process is
|
||||
// managed by ClusterManager or optimizer) then we won't have that info,
|
||||
// so we can't start "legacy" server either.
|
||||
serverOptions:
|
||||
http !== undefined
|
||||
? {
|
||||
...http.options,
|
||||
listener: this.setupProxyListener(http.server),
|
||||
}
|
||||
: { autoListen: false },
|
||||
serverOptions: startDeps.http.isListening()
|
||||
? {
|
||||
...setupDeps.http.options,
|
||||
listener: this.setupProxyListener(setupDeps.http.server),
|
||||
}
|
||||
: { autoListen: false },
|
||||
handledConfigPaths: await this.coreContext.configService.getUsedPaths(),
|
||||
http,
|
||||
elasticsearch,
|
||||
plugins,
|
||||
setupDeps,
|
||||
startDeps,
|
||||
});
|
||||
|
||||
// The kbnWorkerType check is necessary to prevent the repl
|
||||
|
|
|
@ -23,6 +23,7 @@ import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../config';
|
|||
import { getEnvOptions } from '../config/__mocks__/env';
|
||||
import { CoreContext } from '../core_context';
|
||||
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
|
||||
import { PluginWrapper, PluginManifest } from './plugin';
|
||||
|
@ -59,7 +60,10 @@ function createPluginManifest(manifestProps: Partial<PluginManifest> = {}): Plug
|
|||
let configService: ConfigService;
|
||||
let env: Env;
|
||||
let coreContext: CoreContext;
|
||||
const setupDeps = { elasticsearch: elasticsearchServiceMock.createSetupContract() };
|
||||
const setupDeps = {
|
||||
elasticsearch: elasticsearchServiceMock.createSetupContract(),
|
||||
http: httpServiceMock.createSetupContract(),
|
||||
};
|
||||
beforeEach(() => {
|
||||
env = Env.createDefault(getEnvOptions());
|
||||
|
||||
|
|
|
@ -114,12 +114,6 @@ export function createPluginInitializerContext(
|
|||
};
|
||||
}
|
||||
|
||||
// Added to improve http typings as make { http: Required<HttpSetup> }
|
||||
// Http service is disabled, when Kibana runs in optimizer mode or as dev cluster managed by cluster master.
|
||||
// In theory no plugins shouldn try to access http dependency in this case.
|
||||
function preventAccess() {
|
||||
throw new Error('Cannot use http contract when http server not started');
|
||||
}
|
||||
/**
|
||||
* This returns a facade for `CoreContext` that will be exposed to the plugin `setup` method.
|
||||
* This facade should be safe to use only within `setup` itself.
|
||||
|
@ -144,14 +138,9 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
|
|||
adminClient$: deps.elasticsearch.adminClient$,
|
||||
dataClient$: deps.elasticsearch.dataClient$,
|
||||
},
|
||||
http: deps.http
|
||||
? {
|
||||
registerAuth: deps.http.registerAuth,
|
||||
registerOnRequest: deps.http.registerOnRequest,
|
||||
}
|
||||
: {
|
||||
registerAuth: preventAccess,
|
||||
registerOnRequest: preventAccess,
|
||||
},
|
||||
http: {
|
||||
registerAuth: deps.http.registerAuth,
|
||||
registerOnRequest: deps.http.registerOnRequest,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import { BehaviorSubject, from } from 'rxjs';
|
|||
import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../config';
|
||||
import { getEnvOptions } from '../config/__mocks__/env';
|
||||
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
import { PluginDiscoveryError } from './discovery';
|
||||
import { PluginWrapper } from './plugin';
|
||||
|
@ -37,7 +38,10 @@ let pluginsService: PluginsService;
|
|||
let configService: ConfigService;
|
||||
let env: Env;
|
||||
let mockPluginSystem: jest.Mocked<PluginsSystem>;
|
||||
const setupDeps = { elasticsearch: elasticsearchServiceMock.createSetupContract() };
|
||||
const setupDeps = {
|
||||
elasticsearch: elasticsearchServiceMock.createSetupContract(),
|
||||
http: httpServiceMock.createSetupContract(),
|
||||
};
|
||||
const logger = loggingServiceMock.create();
|
||||
beforeEach(() => {
|
||||
mockPackage.raw = {
|
||||
|
|
|
@ -41,7 +41,7 @@ export interface PluginsServiceSetup {
|
|||
/** @internal */
|
||||
export interface PluginsServiceSetupDeps {
|
||||
elasticsearch: ElasticsearchServiceSetup;
|
||||
http?: HttpServiceSetup;
|
||||
http: HttpServiceSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -80,6 +80,8 @@ export class PluginsService implements CoreService<PluginsServiceSetup> {
|
|||
};
|
||||
}
|
||||
|
||||
public async start() {}
|
||||
|
||||
public async stop() {
|
||||
this.log.debug('Stopping plugins service');
|
||||
await this.pluginsSystem.stopPlugins();
|
||||
|
|
|
@ -24,6 +24,7 @@ import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../config';
|
|||
import { getEnvOptions } from '../config/__mocks__/env';
|
||||
import { CoreContext } from '../core_context';
|
||||
import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { loggingServiceMock } from '../logging/logging_service.mock';
|
||||
import { PluginWrapper, PluginName } from './plugin';
|
||||
import { PluginsSystem } from './plugins_system';
|
||||
|
@ -57,7 +58,10 @@ let pluginsSystem: PluginsSystem;
|
|||
let configService: ConfigService;
|
||||
let env: Env;
|
||||
let coreContext: CoreContext;
|
||||
const setupDeps = { elasticsearch: elasticsearchServiceMock.createSetupContract() };
|
||||
const setupDeps = {
|
||||
elasticsearch: elasticsearchServiceMock.createSetupContract(),
|
||||
http: httpServiceMock.createSetupContract(),
|
||||
};
|
||||
beforeEach(() => {
|
||||
env = Env.createDefault(getEnvOptions());
|
||||
|
||||
|
|
|
@ -53,7 +53,18 @@ export class Root {
|
|||
|
||||
try {
|
||||
await this.setupLogging();
|
||||
await this.server.setup();
|
||||
return await this.server.setup();
|
||||
} catch (e) {
|
||||
await this.shutdown(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async start() {
|
||||
this.log.debug('starting root');
|
||||
|
||||
try {
|
||||
return await this.server.start();
|
||||
} catch (e) {
|
||||
await this.shutdown(e);
|
||||
throw e;
|
||||
|
|
|
@ -74,6 +74,12 @@ export interface CoreSetup {
|
|||
plugins: PluginsServiceSetup;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CoreStart {
|
||||
// (undocumented)
|
||||
http: HttpServiceStart;
|
||||
}
|
||||
|
||||
// @internal
|
||||
export interface DiscoveredPlugin {
|
||||
readonly configPath: ConfigPath;
|
||||
|
@ -108,7 +114,12 @@ export interface ElasticsearchServiceSetup {
|
|||
export type Headers = Record<string, string | string[] | undefined>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type HttpServiceSetup = HttpServerInfo;
|
||||
export type HttpServiceSetup = HttpServerSetup;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface HttpServiceStart {
|
||||
isListening: () => boolean;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class KibanaRequest<Params, Query, Body> {
|
||||
|
|
|
@ -44,13 +44,15 @@ afterEach(() => {
|
|||
jest.clearAllMocks();
|
||||
|
||||
configService.atPath.mockReset();
|
||||
httpService.setup.mockReset();
|
||||
httpService.setup.mockClear();
|
||||
httpService.start.mockClear();
|
||||
httpService.stop.mockReset();
|
||||
elasticsearchService.setup.mockReset();
|
||||
elasticsearchService.stop.mockReset();
|
||||
mockPluginsService.setup.mockReset();
|
||||
mockPluginsService.stop.mockReset();
|
||||
mockLegacyService.setup.mockReset();
|
||||
mockLegacyService.start.mockReset();
|
||||
mockLegacyService.stop.mockReset();
|
||||
});
|
||||
|
||||
|
@ -63,54 +65,39 @@ test('sets up services on "setup"', async () => {
|
|||
expect(httpService.setup).not.toHaveBeenCalled();
|
||||
expect(elasticsearchService.setup).not.toHaveBeenCalled();
|
||||
expect(mockPluginsService.setup).not.toHaveBeenCalled();
|
||||
expect(mockLegacyService.setup).not.toHaveBeenCalled();
|
||||
expect(mockLegacyService.start).not.toHaveBeenCalled();
|
||||
|
||||
await server.setup();
|
||||
|
||||
expect(httpService.setup).toHaveBeenCalledTimes(1);
|
||||
expect(elasticsearchService.setup).toHaveBeenCalledTimes(1);
|
||||
expect(mockPluginsService.setup).toHaveBeenCalledTimes(1);
|
||||
expect(mockLegacyService.setup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('runs services on "start"', async () => {
|
||||
const mockPluginsServiceSetup = new Map([['some-plugin', 'some-value']]);
|
||||
mockPluginsService.setup.mockReturnValue(Promise.resolve(mockPluginsServiceSetup));
|
||||
|
||||
const server = new Server(configService as any, logger, env);
|
||||
|
||||
expect(httpService.setup).not.toHaveBeenCalled();
|
||||
expect(mockLegacyService.start).not.toHaveBeenCalled();
|
||||
|
||||
await server.setup();
|
||||
await server.start();
|
||||
|
||||
expect(httpService.start).toHaveBeenCalledTimes(1);
|
||||
expect(mockLegacyService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('does not fail on "setup" if there are unused paths detected', async () => {
|
||||
configService.getUnusedPaths.mockResolvedValue(['some.path', 'another.path']);
|
||||
|
||||
const server = new Server(configService as any, logger, env);
|
||||
await expect(server.setup()).resolves.toBeUndefined();
|
||||
await expect(server.setup()).resolves.toBeDefined();
|
||||
expect(loggingServiceMock.collect(logger)).toMatchSnapshot('unused paths logs');
|
||||
});
|
||||
|
||||
test('does not setup http service is `autoListen:false`', async () => {
|
||||
configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: false }));
|
||||
|
||||
const server = new Server(configService as any, logger, env);
|
||||
|
||||
expect(mockLegacyService.setup).not.toHaveBeenCalled();
|
||||
|
||||
await server.setup();
|
||||
|
||||
expect(httpService.setup).not.toHaveBeenCalled();
|
||||
expect(mockLegacyService.setup).toHaveBeenCalledTimes(1);
|
||||
expect(mockLegacyService.setup).toHaveBeenCalledWith({});
|
||||
});
|
||||
|
||||
test('does not setup http service if process is dev cluster master', async () => {
|
||||
const server = new Server(
|
||||
configService as any,
|
||||
logger,
|
||||
new Env('.', getEnvOptions({ isDevClusterMaster: true }))
|
||||
);
|
||||
|
||||
expect(mockLegacyService.setup).not.toHaveBeenCalled();
|
||||
|
||||
await server.setup();
|
||||
|
||||
expect(httpService.setup).not.toHaveBeenCalled();
|
||||
expect(mockLegacyService.setup).toHaveBeenCalledTimes(1);
|
||||
expect(mockLegacyService.setup).toHaveBeenCalledWith({});
|
||||
});
|
||||
|
||||
test('stops services on "stop"', async () => {
|
||||
const server = new Server(configService as any, logger, env);
|
||||
|
||||
|
|
|
@ -17,10 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { first } from 'rxjs/operators';
|
||||
import { ConfigService, Env } from './config';
|
||||
import { ElasticsearchService } from './elasticsearch';
|
||||
import { HttpConfig, HttpService, HttpServiceSetup, Router } from './http';
|
||||
import { HttpService, HttpServiceSetup, Router } from './http';
|
||||
import { LegacyService } from './legacy';
|
||||
import { Logger, LoggerFactory } from './logging';
|
||||
import { PluginsService } from './plugins';
|
||||
|
@ -32,19 +31,10 @@ export class Server {
|
|||
private readonly legacy: LegacyService;
|
||||
private readonly log: Logger;
|
||||
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
logger: LoggerFactory,
|
||||
private readonly env: Env
|
||||
) {
|
||||
this.log = logger.get('server');
|
||||
|
||||
this.http = new HttpService(configService.atPath('server', HttpConfig), logger);
|
||||
const router = new Router('/core');
|
||||
router.get({ path: '/', validate: false }, async (req, res) => res.ok({ version: '0.0.1' }));
|
||||
this.http.registerRouter(router);
|
||||
|
||||
constructor(configService: ConfigService, logger: LoggerFactory, env: Env) {
|
||||
const core = { env, configService, logger };
|
||||
this.log = logger.get('server');
|
||||
this.http = new HttpService(core);
|
||||
this.plugins = new PluginsService(core);
|
||||
this.legacy = new LegacyService(core);
|
||||
this.elasticsearch = new ElasticsearchService(core);
|
||||
|
@ -53,31 +43,36 @@ export class Server {
|
|||
public async setup() {
|
||||
this.log.debug('setting up server');
|
||||
|
||||
// We shouldn't set up http service in two cases:
|
||||
// 1. If `server.autoListen` is explicitly set to `false`.
|
||||
// 2. When the process is run as dev cluster master in which case cluster manager
|
||||
// will fork a dedicated process where http service will be set up instead.
|
||||
let httpSetup: HttpServiceSetup | undefined;
|
||||
const httpConfig = await this.configService
|
||||
.atPath('server', HttpConfig)
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
if (!this.env.isDevClusterMaster && httpConfig.autoListen) {
|
||||
httpSetup = await this.http.setup();
|
||||
}
|
||||
const httpSetup = await this.http.setup();
|
||||
this.registerDefaultRoute(httpSetup);
|
||||
|
||||
const elasticsearchServiceSetup = await this.elasticsearch.setup();
|
||||
|
||||
const pluginsSetup = await this.plugins.setup({
|
||||
elasticsearch: elasticsearchServiceSetup,
|
||||
http: httpSetup,
|
||||
});
|
||||
|
||||
await this.legacy.setup({
|
||||
const coreSetup = {
|
||||
elasticsearch: elasticsearchServiceSetup,
|
||||
http: httpSetup,
|
||||
plugins: pluginsSetup,
|
||||
});
|
||||
};
|
||||
|
||||
await this.legacy.setup(coreSetup);
|
||||
|
||||
return coreSetup;
|
||||
}
|
||||
|
||||
public async start() {
|
||||
const httpStart = await this.http.start();
|
||||
|
||||
const startDeps = {
|
||||
http: httpStart,
|
||||
};
|
||||
|
||||
await this.legacy.start(startDeps);
|
||||
|
||||
return startDeps;
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
|
@ -88,4 +83,10 @@ export class Server {
|
|||
await this.elasticsearch.stop();
|
||||
await this.http.stop();
|
||||
}
|
||||
|
||||
private registerDefaultRoute(httpSetup: HttpServiceSetup) {
|
||||
const router = new Router('/core');
|
||||
router.get({ path: '/', validate: false }, async (req, res) => res.ok({ version: '0.0.1' }));
|
||||
httpSetup.registerRouter(router);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
*/
|
||||
|
||||
/** @internal */
|
||||
export interface CoreService<TSetup = void> {
|
||||
export interface CoreService<TSetup = void, TStart = void> {
|
||||
setup(...params: any[]): Promise<TSetup>;
|
||||
start(...params: any[]): Promise<TStart>;
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import { createRoot } from '../../../../../test_utils/kbn_server';
|
|||
// to allow the process to exit naturally
|
||||
try {
|
||||
await root.setup();
|
||||
await root.start();
|
||||
} finally {
|
||||
await root.shutdown();
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ beforeAll(async () => {
|
|||
root = kbnTestServer.createRoot({ server: { maxPayloadBytes: 100 } });
|
||||
|
||||
await root.setup();
|
||||
await root.start();
|
||||
|
||||
kbnTestServer.getKbnServer(root).server.route({
|
||||
path: '/payload_size_check/test/route',
|
||||
|
|
|
@ -31,6 +31,7 @@ describe('version_check request filter', function () {
|
|||
root = kbnTestServer.createRoot();
|
||||
|
||||
await root.setup();
|
||||
await root.start();
|
||||
|
||||
kbnTestServer.getKbnServer(root).server.route({
|
||||
path: '/version_check/test/route',
|
||||
|
|
|
@ -39,6 +39,7 @@ describe('xsrf request filter', () => {
|
|||
});
|
||||
|
||||
await root.setup();
|
||||
await root.start();
|
||||
|
||||
const kbnServer = kbnTestServer.getKbnServer(root);
|
||||
kbnServer.server.route({
|
||||
|
|
8
src/legacy/server/kbn_server.d.ts
vendored
8
src/legacy/server/kbn_server.d.ts
vendored
|
@ -22,6 +22,7 @@ import { ResponseObject, Server } from 'hapi';
|
|||
import {
|
||||
ElasticsearchServiceSetup,
|
||||
HttpServiceSetup,
|
||||
HttpServiceStart,
|
||||
ConfigService,
|
||||
PluginsServiceSetup,
|
||||
} from '../../core/server';
|
||||
|
@ -77,10 +78,15 @@ export default class KbnServer {
|
|||
setup: {
|
||||
core: {
|
||||
elasticsearch: ElasticsearchServiceSetup;
|
||||
http?: HttpServiceSetup;
|
||||
http: HttpServiceSetup;
|
||||
};
|
||||
plugins: PluginsServiceSetup;
|
||||
};
|
||||
start: {
|
||||
core: {
|
||||
http: HttpServiceStart;
|
||||
};
|
||||
};
|
||||
stop: null;
|
||||
params: {
|
||||
serverOptions: ElasticsearchServiceSetup;
|
||||
|
|
|
@ -54,14 +54,19 @@ export default class KbnServer {
|
|||
this.rootDir = rootDir;
|
||||
this.settings = settings || {};
|
||||
|
||||
const { plugins, http, elasticsearch, serverOptions, handledConfigPaths } = core;
|
||||
const { setupDeps, startDeps, serverOptions, handledConfigPaths } = core;
|
||||
this.newPlatform = {
|
||||
setup: {
|
||||
core: {
|
||||
elasticsearch,
|
||||
http,
|
||||
elasticsearch: setupDeps.elasticsearch,
|
||||
http: setupDeps.http,
|
||||
},
|
||||
plugins: setupDeps.plugins,
|
||||
},
|
||||
start: {
|
||||
core: {
|
||||
http: startDeps.http,
|
||||
},
|
||||
plugins,
|
||||
},
|
||||
stop: null,
|
||||
params: {
|
||||
|
|
|
@ -61,6 +61,7 @@ describe('UiExports', function () {
|
|||
});
|
||||
|
||||
await root.setup();
|
||||
await root.start();
|
||||
|
||||
kbnServer = getKbnServer(root);
|
||||
|
||||
|
|
|
@ -236,6 +236,7 @@ export async function startTestServers({
|
|||
const root = createRootWithCorePlugins(kbnSettings);
|
||||
|
||||
await root.setup();
|
||||
await root.start();
|
||||
|
||||
const kbnServer = getKbnServer(root);
|
||||
await kbnServer.server.plugins.elasticsearch.waitUntilReady();
|
||||
|
|
Loading…
Reference in a new issue