Skip to content

Commit

Permalink
feat(rest): allow controllers/routes to be added/removed after server…
Browse files Browse the repository at this point in the history
… is started

Implements #433
  • Loading branch information
raymondfeng committed Feb 18, 2020
1 parent 3edd131 commit 8f55a70
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 7 deletions.
51 changes: 49 additions & 2 deletions packages/rest/src/__tests__/integration/rest.server.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -887,15 +887,62 @@ paths:
const server = await givenAServer();
server.controller(DummyController);
await server.start();
const keys = server.find('routes.*').map(b => b.key);
expect(keys).to.eql(['routes.get %2Fhtml', 'routes.get %2Fendpoint']);
const keys = server.find('controller-routes.*').map(b => b.key);
expect(keys).to.eql([
'controller-routes.get %2Fhtml',
'controller-routes.get %2Fendpoint',
]);
for (const key of keys) {
const controllerRoute = await server.get(key);
expect(controllerRoute).to.be.instanceOf(ControllerRoute);
}
await server.stop();
});

it('register controller routes after start', async () => {
const server = await givenAServer();
await server.start();
// No DummyController is present
await createClientForHandler(server.requestHandler)
.get('/html')
.expect(404);

// Add DummyController after server.start
server.controller(DummyController);

// Now /html is available
await createClientForHandler(server.requestHandler)
.get('/html')
.expect(200);

// The controller contributes `controller-routes`
const keys = server.find('controller-routes.*').map(b => b.key);
expect(keys).to.eql([
'controller-routes.get %2Fhtml',
'controller-routes.get %2Fendpoint',
]);
await server.stop();
});

it('remove controller routes after start', async () => {
const server = await givenAServer();
const binding = server.controller(DummyController);
await server.start();
// Now /html is available
await createClientForHandler(server.requestHandler)
.get('/html')
.expect(200);

// Remove DummyController
server.unbind(binding.key);

// Now /html is not available
await createClientForHandler(server.requestHandler)
.get('/html')
.expect(404);
await server.stop();
});

it('creates a redirect route with the default status code', async () => {
const server = await givenAServer();
server.controller(DummyController);
Expand Down
28 changes: 23 additions & 5 deletions packages/rest/src/rest.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
BindingScope,
Constructor,
Context,
ContextView,
filterByKey,
inject,
} from '@loopback/context';
import {Application, CoreBindings, Server} from '@loopback/core';
Expand Down Expand Up @@ -146,6 +148,8 @@ export class RestServer extends Context implements Server, HttpServerLike {
this._setupHandlerIfNeeded();
return this._httpHandler;
}
private _controllersAndRoutesView?: ContextView;

protected _httpServer: HttpServer | undefined;

protected _expressApp: express.Application;
Expand Down Expand Up @@ -338,12 +342,25 @@ export class RestServer extends Context implements Server, HttpServerLike {
}

protected _setupHandlerIfNeeded() {
// TODO(bajtos) support hot-reloading of controllers
// after the app started. The idea is to rebuild the HttpHandler
// instance whenever a controller was added/deleted.
// See https://github.com/strongloop/loopback-next/issues/433
if (this._httpHandler) return;
if (this._controllersAndRoutesView) this._controllersAndRoutesView.close();
this._createHttpHandler();
// Watch for binding events
this._controllersAndRoutesView = this.createView(
binding =>
filterByKey(RestBindings.API_SPEC.key)(binding) ||
filterByKey(/^(controllers|routes)\..+/)(binding),
);

// See https://github.com/strongloop/loopback-next/issues/433
this._controllersAndRoutesView.on('refresh', () => {
// Rebuild the HttpHandler instance whenever a controller/route was
// added/deleted.
this._createHttpHandler();
});
}

private _createHttpHandler() {
/**
* Check if there is custom router in the context
*/
Expand Down Expand Up @@ -377,7 +394,8 @@ export class RestServer extends Context implements Server, HttpServerLike {
controllerFactory,
);
for (const route of routes) {
this.bindRoute(route);
this.bindRoute(route, 'controller-routes');
this._httpHandler.registerRoute(route);
}
}

Expand Down

0 comments on commit 8f55a70

Please sign in to comment.