diff --git a/platform/server/elasticsearch/api.ts b/platform/server/elasticsearch/api.ts index d870491c331c173..525395a230dd783 100644 --- a/platform/server/elasticsearch/api.ts +++ b/platform/server/elasticsearch/api.ts @@ -1,13 +1,13 @@ import { logger } from '../../logger' -import { Routing } from '../http'; +import { RouterFactory } from '../http'; import { Schema } from '../../types'; const log = logger.get('elasticsearch', 'api') -export function createElasticsearchRoutes(routing: Routing, schema: Schema) { +export function createElasticsearchRoutes(routerFactory: RouterFactory, schema: Schema) { log.info('creating elasticsearch api'); - const router = routing.createRouter('/plugin/elasticsearch'); + const router = routerFactory.createRouter('/plugin/elasticsearch'); router.get({ path: '/:field', @@ -38,4 +38,6 @@ export function createElasticsearchRoutes(routing: Routing, schema: Schema) { query: req.query }; }); + + return router; } diff --git a/platform/server/elasticsearch/index.ts b/platform/server/elasticsearch/index.ts index f155ba65dffd269..38b1260372d6103 100644 --- a/platform/server/elasticsearch/index.ts +++ b/platform/server/elasticsearch/index.ts @@ -3,7 +3,7 @@ import { Observable } from 'rxjs'; import { ElasticsearchService } from './ElasticsearchService'; import { ElasticsearchFacade } from './ElasticsearchFacade'; import { createElasticsearchRoutes } from './api'; -import { Routing } from '../http'; +import { RouterFactory } from '../http'; import { IsRouteable } from '../../types'; import { Config } from '../../config'; import { Schema } from '../../types'; @@ -25,7 +25,7 @@ export class ElasticsearchModule implements IsRouteable { this.facade = new ElasticsearchFacade(this.service); } - createRoutes(routing: Routing, schema: Schema) { - createElasticsearchRoutes(routing, schema); + createRoutes(routerFactory: RouterFactory, schema: Schema) { + return createElasticsearchRoutes(routerFactory, schema); } } diff --git a/platform/server/http/HttpService.ts b/platform/server/http/HttpService.ts index c62fea343f5eef2..c7ce0260535561e 100644 --- a/platform/server/http/HttpService.ts +++ b/platform/server/http/HttpService.ts @@ -5,7 +5,7 @@ import { Observable, Subscription } from 'rxjs'; import { HttpConfig } from './HttpConfig'; import { Service } from '../../types'; import { logger } from '../../logger'; -import { Routing } from './Routing'; +import { Router } from './Routing'; const log = logger.get('http'); @@ -14,12 +14,23 @@ export class HttpService implements Service { private readonly httpServer: http.Server; private readonly server$: Observable; + private readonly routers$: Observable; private subscription?: Subscription; - constructor(config$: Observable) { + constructor( + config$: Observable, + router$: Observable + ) { this.app = express(); this.httpServer = http.createServer(this.app); + this.routers$ = router$ + .scan((routers, route) => { + routers.push(route); + return routers; + }, []) + .last(); + this.server$ = config$ // TODO Add an `equals` check to the http config // .distinctUntilChanged((prev, next) => false) @@ -38,11 +49,11 @@ export class HttpService implements Service { }) } - start(routing: Routing) { - this.subscription = this.server$ - .switchMap(config => + start() { + this.subscription = Observable.combineLatest(this.server$, this.routers$) + .switchMap(([config, routers]) => new Observable(() => { - this.startHttpServer(config, routing); + this.startHttpServer(config, routers); return () => { // TODO: This is async! :/ @@ -59,10 +70,10 @@ export class HttpService implements Service { } } - private startHttpServer(config: HttpConfig, routing: Routing) { + private startHttpServer(config: HttpConfig, routers: Router[]) { const { host, port } = config; - routing.routers.forEach(router => { + routers.forEach(router => { log.info(`registering route handler for [${router.path}]`); this.app.use(router.path, router.router); }); diff --git a/platform/server/http/Routing.ts b/platform/server/http/Routing.ts index 7a5058b6dbbade7..2be429448fce366 100644 --- a/platform/server/http/Routing.ts +++ b/platform/server/http/Routing.ts @@ -158,15 +158,11 @@ export class Router { } } -export class Routing { - readonly routers: Router[] = []; - +export class RouterFactory { constructor(private readonly kibana: KibanaRequestHelpers) { } createRouter(path: string) { - const router = new Router(path, this.kibana) - this.routers.push(router); - return router; + return new Router(path, this.kibana) } } diff --git a/platform/server/http/index.ts b/platform/server/http/index.ts index 953b1b7f343c320..bb55beedb1ff87b 100644 --- a/platform/server/http/index.ts +++ b/platform/server/http/index.ts @@ -3,19 +3,20 @@ import { Observable } from 'rxjs'; import { Config } from '../../config'; import { HttpService } from './HttpService'; import { HttpConfig } from './HttpConfig'; +import { Router } from './Routing'; export { httpSchema } from './HttpConfig'; -export { Routing, KibanaRequest, KibanaResponse } from './Routing'; -export { HttpService }; +export { RouterFactory, KibanaRequest, KibanaResponse } from './Routing'; +export { HttpService, Router }; export class HttpModule { service: HttpService; config$: Observable; - constructor(config$: Observable) { + constructor(config$: Observable, router$: Observable) { this.config$ = config$.map(config => new HttpConfig(config.atPath('server'), config.env) ); - this.service = new HttpService(this.config$); + this.service = new HttpService(this.config$, router$); } } \ No newline at end of file diff --git a/platform/server/index.ts b/platform/server/index.ts index f0abf21db29f439..622e0f567edd3a6 100644 --- a/platform/server/index.ts +++ b/platform/server/index.ts @@ -1,6 +1,6 @@ -import { Observable } from 'rxjs'; +import { Observable, ReplaySubject } from 'rxjs'; -import { HttpModule, Routing } from './http'; +import { HttpModule, RouterFactory, Router } from './http'; import { PidModule } from './pid'; import { SavedObjectsModule } from './savedObjects'; import { ElasticsearchModule } from './elasticsearch'; @@ -12,12 +12,16 @@ import { IsRouteable } from '../types'; const log = logger.get('server'); +const toArray = (items: T | T[]) => + Array.isArray(items) ? items : [items]; + export class Server { private readonly elasticsearch: ElasticsearchModule; private readonly http: HttpModule; private readonly pid: PidModule; private readonly savedObjects: SavedObjectsModule; private readonly kibana: KibanaModule; + private readonly isRouteable$: ReplaySubject; constructor( private readonly config$: Observable @@ -29,19 +33,8 @@ export class Server { this.kibana.config$, this.elasticsearch.service ); - this.http = new HttpModule(config$); - } - - start() { - log.info('starting server :tada:'); - const coreRoutes: IsRouteable[] = [ - this.elasticsearch, - this.savedObjects - ]; - - // start services - this.elasticsearch.service.start(); + this.isRouteable$ = new ReplaySubject(); // We make this object available in all requests. It's basically "helpers" // so we don't need to handle observables directly in the requests, but @@ -56,19 +49,31 @@ export class Server { savedObjects: this.savedObjects.facade }; - // Routing depends on other services, so must start after most of them - const routing = new Routing(kibana); - coreRoutes.forEach(routesFactory => { - routesFactory.createRoutes(routing, schema); - }); + const routerFactory = new RouterFactory(kibana); + const routers = this.isRouteable$ + .flatMap(routable => + toArray(routable.createRoutes(routerFactory, schema)) + ); + + this.http = new HttpModule(config$, routers); + } + + start() { + log.info('starting server :tada:'); + + // TODO loop through all modules and `next` all `IsRoutable`s. + this.isRouteable$.next(this.elasticsearch); + this.isRouteable$.next(this.savedObjects); + + // start services + this.elasticsearch.service.start(); // we finish by starting the rest of the services this.pid.service.start(); - // TODO: Looks like this param can now be pushed into constructor instead - // Hm, maybe we can rely on observables here? - // e.g. `Observable` or something like that - this.http.service.start(routing); + // We're about to start the http services, so we no longer allow new routes + this.isRouteable$.complete(); + this.http.service.start(); } stop() { diff --git a/platform/server/savedObjects/api.ts b/platform/server/savedObjects/api.ts index dcfb805b9185be8..fce1618113eb9e3 100644 --- a/platform/server/savedObjects/api.ts +++ b/platform/server/savedObjects/api.ts @@ -1,13 +1,13 @@ import { logger } from '../../logger' -import { Routing, KibanaResponse } from '../http'; +import { RouterFactory, KibanaResponse } from '../http'; import { Schema } from '../../types'; const log = logger.get('savedObjects', 'api') -export function createSavedObjectsRoutes(routing: Routing, schema: Schema) { +export function createSavedObjectsRoutes(routerFactory: RouterFactory, schema: Schema) { log.info('creating saved objects api'); - const router = routing.createRouter('/saved_objects'); + const router = routerFactory.createRouter('/saved_objects'); router.get({ path: '/fail' @@ -61,4 +61,6 @@ export function createSavedObjectsRoutes(routing: Routing, schema: Schema) { return res.ok(savedObjects); }); + + return router; } diff --git a/platform/server/savedObjects/index.ts b/platform/server/savedObjects/index.ts index 9dabfc004e7ed6d..cc9d089de598133 100644 --- a/platform/server/savedObjects/index.ts +++ b/platform/server/savedObjects/index.ts @@ -4,7 +4,7 @@ import { SavedObjectsFacade } from './SavedObjectsFacade'; import { ElasticsearchService } from '../elasticsearch'; import { KibanaConfig } from '../kibana'; import { Config } from '../../config'; -import { Routing } from '../http/Routing'; +import { RouterFactory } from '../http/Routing'; import { IsRouteable } from '../../types'; import { Schema } from '../../types'; @@ -24,7 +24,7 @@ export class SavedObjectsModule implements IsRouteable { ) } - createRoutes(routing: Routing, schema: Schema): void { - createSavedObjectsRoutes(routing, schema); + createRoutes(routerFactory: RouterFactory, schema: Schema) { + return createSavedObjectsRoutes(routerFactory, schema); } } \ No newline at end of file diff --git a/platform/types.ts b/platform/types.ts index 509daecdda92f34..f4304de9ba9d48c 100644 --- a/platform/types.ts +++ b/platform/types.ts @@ -1,5 +1,5 @@ import * as schema from './lib/schema'; -import { Routing } from './server/http'; +import { RouterFactory, Router } from './server/http'; import { ElasticsearchFacade } from './server/elasticsearch'; import { SavedObjectsFacade } from './server/savedObjects'; @@ -11,7 +11,7 @@ export interface Service { } export interface IsRouteable { - createRoutes(routing: Routing, schema: Schema): void; + createRoutes(routerFactory: RouterFactory, schema: Schema): Router | Router[]; } export type KibanaRequestHelpers = {