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