Skip to content

Commit 20a41ac

Browse files
committed
feat(rest): expose app.requestHandler function
Each RestServer provides an HTTP handler function that can be used with the core HTTP server or any compatible framework like Express. This change exposes the handler function on the RestApplication class to simplify the usage. BREAKING CHANGE: `RestServer#handleHttp` was renamed to `RestServer#requestHandler`.
1 parent a219da3 commit 20a41ac

File tree

11 files changed

+94
-50
lines changed

11 files changed

+94
-50
lines changed

packages/authentication/test/acceptance/basic-auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ describe('Basic Authentication', () => {
188188
}
189189

190190
function whenIMakeRequestTo(restServer: RestServer): Client {
191-
return createClientForHandler(restServer.handleHttp);
191+
return createClientForHandler(restServer.requestHandler);
192192
}
193193
});
194194

packages/boot/test/acceptance/controller.booter.acceptance.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('controller booter acceptance tests', () => {
2323
await app.start();
2424

2525
const server: RestServer = await app.getServer(RestServer);
26-
const client: Client = createClientForHandler(server.handleHttp);
26+
const client: Client = createClientForHandler(server.requestHandler);
2727

2828
// Default Controllers = /controllers with .controller.js ending (nested = true);
2929
await client.get('/one').expect(200, 'ControllerOne.one()');

packages/cli/generators/app/templates/test/ping.controller.test.ts.ejs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe('PingController', () => {
1717
});
1818

1919
before(() => {
20-
client = createClientForHandler(server.handleHttp);
20+
client = createClientForHandler(server.requestHandler);
2121
});
2222

2323
after(async () => {

packages/cli/test/app-run.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const helpers = require('yeoman-test');
1111
const lerna = require('lerna');
1212
const build = require('@loopback/build');
1313

14-
describe('app-generator', function() {
14+
describe('app-generator (SLOW)', function() {
1515
const generator = path.join(__dirname, '../generators/app');
1616
const rootDir = path.join(__dirname, '../../..');
1717
const sandbox = path.join(__dirname, '../../_sandbox');

packages/example-getting-started/test/acceptance/application.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ describe('Application', () => {
1919
before(givenARestServer);
2020
before(givenTodoRepository);
2121
before(() => {
22-
client = createClientForHandler(server.handleHttp);
22+
client = createClientForHandler(server.requestHandler);
2323
});
2424
after(async () => {
2525
await app.stop();

packages/example-log-extension/test/acceptance/log.extension.acceptance.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import {
77
RestApplication,
8-
RestServer,
98
SequenceHandler,
109
RestBindings,
1110
FindRoute,
@@ -42,7 +41,6 @@ import {logToMemory, resetLogs} from '../in-memory-logger';
4241

4342
describe('log extension acceptance test', () => {
4443
let app: LogApp;
45-
let server: RestServer;
4644
let spy: SinonSpy;
4745

4846
class LogApp extends LogMixin(RestApplication) {}
@@ -73,7 +71,7 @@ describe('log extension acceptance test', () => {
7371

7472
it('logs information at DEBUG or higher', async () => {
7573
setAppLogToDebug();
76-
const client: Client = createClientForHandler(server.handleHttp);
74+
const client: Client = createClientForHandler(app.requestHandler);
7775

7876
await client.get('/nolog').expect(200, 'nolog called');
7977
expect(spy.called).to.be.False();
@@ -99,7 +97,7 @@ describe('log extension acceptance test', () => {
9997

10098
it('logs information at INFO or higher', async () => {
10199
setAppLogToInfo();
102-
const client: Client = createClientForHandler(server.handleHttp);
100+
const client: Client = createClientForHandler(app.requestHandler);
103101

104102
await client.get('/nolog').expect(200, 'nolog called');
105103
expect(spy.called).to.be.False();
@@ -125,7 +123,7 @@ describe('log extension acceptance test', () => {
125123

126124
it('logs information at WARN or higher', async () => {
127125
setAppLogToWarn();
128-
const client: Client = createClientForHandler(server.handleHttp);
126+
const client: Client = createClientForHandler(app.requestHandler);
129127

130128
await client.get('/nolog').expect(200, 'nolog called');
131129
expect(spy.called).to.be.False();
@@ -151,7 +149,7 @@ describe('log extension acceptance test', () => {
151149

152150
it('logs information at ERROR', async () => {
153151
setAppLogToError();
154-
const client: Client = createClientForHandler(server.handleHttp);
152+
const client: Client = createClientForHandler(app.requestHandler);
155153

156154
await client.get('/nolog').expect(200, 'nolog called');
157155
expect(spy.called).to.be.False();
@@ -177,7 +175,7 @@ describe('log extension acceptance test', () => {
177175

178176
it('logs no information when logLevel is set to OFF', async () => {
179177
setAppLogToOff();
180-
const client: Client = createClientForHandler(server.handleHttp);
178+
const client: Client = createClientForHandler(app.requestHandler);
181179

182180
await client.get('/nolog').expect(200, 'nolog called');
183181
expect(spy.called).to.be.False();
@@ -234,14 +232,13 @@ describe('log extension acceptance test', () => {
234232
}
235233
}
236234

237-
server.sequence(LogSequence);
235+
app.sequence(LogSequence);
238236
}
239237

240238
async function createApp() {
241239
app = new LogApp();
242240
app.bind(EXAMPLE_LOG_BINDINGS.TIMER).to(timer);
243241
app.bind(EXAMPLE_LOG_BINDINGS.LOGGER).to(logToMemory);
244-
server = await app.getServer(RestServer);
245242
}
246243

247244
function setAppLogToDebug() {

packages/rest/src/rest-application.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {SequenceHandler, SequenceFunction} from './sequence';
99
import {Binding, Constructor} from '@loopback/context';
1010
import {format} from 'util';
1111
import {RestBindings} from './keys';
12-
import {RouteEntry, RestServer} from '.';
12+
import {RouteEntry, RestServer, HttpRequestListener, HttpServerLike} from '.';
1313
import {ControllerClass} from './router/routing-table';
1414
import {OperationObject, OpenApiSpec} from '@loopback/openapi-v3-types';
1515

@@ -28,7 +28,37 @@ export const SequenceActions = RestBindings.SequenceActions;
2828
* will throw an error.
2929
*
3030
*/
31-
export class RestApplication extends Application {
31+
export class RestApplication extends Application implements HttpServerLike {
32+
/**
33+
* The main REST server instance providing REST API for this application.
34+
*/
35+
get restServer(): RestServer {
36+
// FIXME(kjdelisle): I attempted to mimic the pattern found in RestServer
37+
// with no success, so until I've got a better way, this is functional.
38+
return this.getSync<RestServer>('servers.RestServer');
39+
}
40+
41+
/**
42+
* Handle incoming HTTP(S) request by invoking the corresponding
43+
* Controller method via the configured Sequence.
44+
*
45+
* @example
46+
*
47+
* ```ts
48+
* const app = new RestApplication();
49+
* // setup controllers, etc.
50+
*
51+
* const server = http.createServer(app.requestHandler);
52+
* server.listen(3000);
53+
* ```
54+
*
55+
* @param req The request.
56+
* @param res The response.
57+
*/
58+
get requestHandler(): HttpRequestListener {
59+
return this.restServer.requestHandler;
60+
}
61+
3262
constructor(config?: ApplicationConfig) {
3363
const cfg = Object.assign({}, config);
3464
super(cfg);
@@ -47,10 +77,7 @@ export class RestApplication extends Application {
4777
}
4878

4979
handler(handlerFn: SequenceFunction) {
50-
// FIXME(kjdelisle): I attempted to mimic the pattern found in RestServer
51-
// with no success, so until I've got a better way, this is functional.
52-
const server: RestServer = this.getSync('servers.RestServer');
53-
server.handler(handlerFn);
80+
this.restServer.handler(handlerFn);
5481
}
5582

5683
/**
@@ -101,8 +128,7 @@ export class RestApplication extends Application {
101128
controller?: ControllerClass,
102129
methodName?: string,
103130
): Binding {
104-
// FIXME(bajtos): This is a workaround based on app.handler() above
105-
const server: RestServer = this.getSync('servers.RestServer');
131+
const server = this.restServer;
106132
if (typeof routeOrVerb === 'object') {
107133
return server.route(routeOrVerb);
108134
} else {

packages/rest/src/rest-server.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ import {
2626
import {ControllerClass} from './router/routing-table';
2727
import {RestBindings} from './keys';
2828

29+
export type HttpRequestListener = (
30+
req: ServerRequest,
31+
res: ServerResponse,
32+
) => void;
33+
34+
export interface HttpServerLike {
35+
requestHandler: HttpRequestListener;
36+
}
37+
2938
const SequenceActions = RestBindings.SequenceActions;
3039

3140
// NOTE(bajtos) we cannot use `import * as cloneDeep from 'lodash/cloneDeep'
@@ -80,7 +89,7 @@ const OPENAPI_SPEC_MAPPING: {[key: string]: OpenApiSpecOptions} = {
8089
* @extends {Context}
8190
* @implements {Server}
8291
*/
83-
export class RestServer extends Context implements Server {
92+
export class RestServer extends Context implements Server, HttpServerLike {
8493
/**
8594
* Handle incoming HTTP(S) request by invoking the corresponding
8695
* Controller method via the configured Sequence.
@@ -89,16 +98,18 @@ export class RestServer extends Context implements Server {
8998
*
9099
* ```ts
91100
* const app = new Application();
101+
* app.component(RestComponent);
92102
* // setup controllers, etc.
93103
*
94-
* const server = http.createServer(app.handleHttp);
95-
* server.listen(3000);
104+
* const restServer = await app.getServer(RestServer);
105+
* const httpServer = http.createServer(restServer.requestHandler);
106+
* httpServer.listen(3000);
96107
* ```
97108
*
98109
* @param req The request.
99110
* @param res The response.
100111
*/
101-
public handleHttp: (req: ServerRequest, res: ServerResponse) => void;
112+
public requestHandler: HttpRequestListener;
102113

103114
protected _httpHandler: HttpHandler;
104115
protected get httpHandler(): HttpHandler {
@@ -140,7 +151,7 @@ export class RestServer extends Context implements Server {
140151
this.sequence(options.sequence);
141152
}
142153

143-
this.handleHttp = (req: ServerRequest, res: ServerResponse) => {
154+
this.requestHandler = (req: ServerRequest, res: ServerResponse) => {
144155
try {
145156
this._handleHttpRequest(req, res, options!).catch(err =>
146157
this._onUnhandledError(req, res, err),
@@ -540,7 +551,7 @@ export class RestServer extends Context implements Server {
540551

541552
const httpPort = await this.get<number>(RestBindings.PORT);
542553
const httpHost = await this.get<string | undefined>(RestBindings.HOST);
543-
this._httpServer = createServer(this.handleHttp);
554+
this._httpServer = createServer(this.requestHandler);
544555
const httpServer = this._httpServer;
545556

546557
// TODO(bajtos) support httpHostname too

packages/rest/test/acceptance/routing/routing.acceptance.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
RestComponent,
1313
RestApplication,
1414
SequenceActions,
15+
HttpServerLike,
1516
} from '../../..';
1617

1718
import {api, get, post, param, requestBody} from '@loopback/openapi-v3';
@@ -552,9 +553,9 @@ describe('Routing', () => {
552553
const route = new Route('get', '/greet', routeSpec, greet);
553554
app.route(route);
554555

555-
const server = await givenAServer(app);
556-
const client = whenIMakeRequestTo(server);
557-
await client.get('/greet?name=world').expect(200, 'hello world');
556+
await whenIMakeRequestTo(app)
557+
.get('/greet?name=world')
558+
.expect(200, 'hello world');
558559
});
559560

560561
it('supports controller routes declared via app.api()', async () => {
@@ -580,9 +581,9 @@ describe('Routing', () => {
580581
app.api(spec);
581582
app.controller(MyController);
582583

583-
const server = await givenAServer(app);
584-
const client = whenIMakeRequestTo(server);
585-
await client.get('/greet?name=world').expect(200, 'hello world');
584+
await whenIMakeRequestTo(app)
585+
.get('/greet?name=world')
586+
.expect(200, 'hello world');
586587
});
587588

588589
it('supports controller routes defined via app.route()', async () => {
@@ -600,9 +601,18 @@ describe('Routing', () => {
600601

601602
app.route('get', '/greet', spec, MyController, 'greet');
602603

603-
const server = await givenAServer(app);
604-
const client = whenIMakeRequestTo(server);
605-
await client.get('/greet?name=world').expect(200, 'hello world');
604+
await whenIMakeRequestTo(app)
605+
.get('/greet?name=world')
606+
.expect(200, 'hello world');
607+
});
608+
609+
it('provides httpHandler compatible with HTTP server API', async () => {
610+
const app = new RestApplication();
611+
app.handler((sequence, req, res) => res.end('hello'));
612+
613+
await createClientForHandler(app.requestHandler)
614+
.get('/')
615+
.expect(200, 'hello');
606616
});
607617
});
608618

@@ -631,7 +641,7 @@ describe('Routing', () => {
631641
app.controller(controller);
632642
}
633643

634-
function whenIMakeRequestTo(server: RestServer): Client {
635-
return createClientForHandler(server.handleHttp);
644+
function whenIMakeRequestTo(serverOrApp: HttpServerLike): Client {
645+
return createClientForHandler(serverOrApp.requestHandler);
636646
}
637647
});

packages/rest/test/acceptance/sequence/sequence.acceptance.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
RestServer,
1818
RestComponent,
1919
RestApplication,
20+
HttpServerLike,
2021
} from '../../..';
2122
import {api} from '@loopback/openapi-v3';
2223
import {Application} from '@loopback/core';
@@ -104,8 +105,7 @@ describe('Sequence', () => {
104105
const restApp = new RestApplication();
105106
restApp.sequence(MySequence);
106107

107-
const appServer = await restApp.getServer(RestServer);
108-
await whenIRequest(appServer)
108+
await whenIRequest(restApp)
109109
.get('/name')
110110
.expect('MySequence was invoked.');
111111
});
@@ -213,7 +213,7 @@ describe('Sequence', () => {
213213
app.controller(controller);
214214
}
215215

216-
function whenIRequest(restServer: RestServer = server): Client {
217-
return createClientForHandler(restServer.handleHttp);
216+
function whenIRequest(restServerOrApp: HttpServerLike = server): Client {
217+
return createClientForHandler(restServerOrApp.requestHandler);
218218
}
219219
});

0 commit comments

Comments
 (0)