Skip to content

Commit 4d8c374

Browse files
committed
feat(http-server): make http2 server creation extensible
1 parent 08445f1 commit 4d8c374

File tree

8 files changed

+350
-125
lines changed

8 files changed

+350
-125
lines changed

packages/http-server/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
"@types/node": "^10.11.2",
2727
"@types/p-event": "^1.3.0",
2828
"@types/request-promise-native": "^1.0.15",
29-
"request-promise-native": "^1.0.5"
29+
"@types/spdy": "^3.4.4",
30+
"request-promise-native": "^1.0.5",
31+
"spdy": "^4.0.0"
3032
},
3133
"files": [
3234
"README.md",

packages/http-server/src/http-server.ts

Lines changed: 90 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,129 +3,104 @@
33
// This file is licensed under the MIT License.
44
// License text available at https://opensource.org/licenses/MIT
55

6-
import {IncomingMessage, ServerResponse} from 'http';
76
import * as http from 'http';
87
import * as https from 'https';
98
import {AddressInfo} from 'net';
10-
119
import * as pEvent from 'p-event';
12-
13-
export type RequestListener = (
14-
req: IncomingMessage,
15-
res: ServerResponse,
16-
) => void;
17-
18-
/**
19-
* Basic HTTP server listener options
20-
*
21-
* @export
22-
* @interface ListenerOptions
23-
*/
24-
export interface ListenerOptions {
25-
host?: string;
26-
port?: number;
27-
}
28-
29-
/**
30-
* HTTP server options
31-
*
32-
* @export
33-
* @interface HttpOptions
34-
*/
35-
export interface HttpOptions extends ListenerOptions {
36-
protocol?: 'http';
37-
}
38-
39-
/**
40-
* HTTPS server options
41-
*
42-
* @export
43-
* @interface HttpsOptions
44-
*/
45-
export interface HttpsOptions extends ListenerOptions, https.ServerOptions {
46-
protocol: 'https';
47-
}
48-
49-
/**
50-
* Possible server options
51-
*
52-
* @export
53-
* @type HttpServerOptions
54-
*/
55-
export type HttpServerOptions = HttpOptions | HttpsOptions;
56-
57-
/**
58-
* Supported protocols
59-
*
60-
* @export
61-
* @type HttpProtocol
62-
*/
63-
export type HttpProtocol = 'http' | 'https'; // Will be extended to `http2` in the future
10+
import {
11+
HttpProtocol,
12+
HttpServer,
13+
HttpServerOptions,
14+
RequestListener,
15+
ProtocolServerFactory,
16+
} from './types';
6417

6518
/**
6619
* HTTP / HTTPS server used by LoopBack's RestServer
6720
*
6821
* @export
6922
* @class HttpServer
7023
*/
71-
export class HttpServer {
24+
export class DefaultHttpServer implements HttpServer {
7225
private _port: number;
7326
private _host?: string;
7427
private _listening: boolean = false;
75-
private _protocol: HttpProtocol;
28+
protected _protocol: string;
29+
private _urlScheme: string;
7630
private _address: AddressInfo;
77-
private requestListener: RequestListener;
78-
readonly server: http.Server | https.Server;
79-
private serverOptions?: HttpServerOptions;
31+
protected readonly requestListener: RequestListener;
32+
protected _server: http.Server | https.Server;
33+
protected readonly serverOptions: HttpServerOptions;
34+
35+
protected protocolServerFactories: ProtocolServerFactory[];
8036

8137
/**
8238
* @param requestListener
8339
* @param serverOptions
8440
*/
8541
constructor(
8642
requestListener: RequestListener,
87-
serverOptions?: HttpServerOptions,
43+
serverOptions: HttpServerOptions = {},
44+
protocolServerFactories?: ProtocolServerFactory[],
8845
) {
8946
this.requestListener = requestListener;
47+
serverOptions = serverOptions || {};
9048
this.serverOptions = serverOptions;
91-
this._port = serverOptions ? serverOptions.port || 0 : 0;
92-
this._host = serverOptions ? serverOptions.host : undefined;
93-
this._protocol = serverOptions ? serverOptions.protocol || 'http' : 'http';
94-
if (this._protocol === 'https') {
95-
this.server = https.createServer(
96-
this.serverOptions as https.ServerOptions,
97-
this.requestListener,
98-
);
99-
} else {
100-
this.server = http.createServer(this.requestListener);
49+
this._port = serverOptions.port || 0;
50+
this._host = serverOptions.host || undefined;
51+
this._protocol = serverOptions.protocol || 'http';
52+
this.protocolServerFactories = protocolServerFactories || [
53+
new HttpProtocolServerFactory(),
54+
];
55+
const server = this.createServer();
56+
this._server = server.server;
57+
this._urlScheme = server.urlScheme;
58+
}
59+
60+
/**
61+
* Create a server for the given protocol
62+
*/
63+
protected createServer() {
64+
for (const factory of this.protocolServerFactories) {
65+
if (factory.supports(this._protocol, this.serverOptions)) {
66+
const server = factory.createServer(
67+
this._protocol,
68+
this.requestListener,
69+
this.serverOptions,
70+
);
71+
if (server) {
72+
return server;
73+
}
74+
}
10175
}
76+
throw new Error(`The ${this._protocol} protocol is not supported`);
10277
}
10378

10479
/**
10580
* Starts the HTTP / HTTPS server
10681
*/
10782
public async start() {
108-
this.server.listen(this._port, this._host);
109-
await pEvent(this.server, 'listening');
83+
this._server.listen(this._port, this._host);
84+
await pEvent(this._server, 'listening');
11085
this._listening = true;
111-
this._address = this.server.address() as AddressInfo;
86+
this._address = this._server.address() as AddressInfo;
11287
}
11388

11489
/**
11590
* Stops the HTTP / HTTPS server
11691
*/
11792
public async stop() {
118-
if (!this.server) return;
119-
this.server.close();
120-
await pEvent(this.server, 'close');
93+
if (!this._server) return;
94+
this._server.close();
95+
await pEvent(this._server, 'close');
12196
this._listening = false;
12297
}
12398

12499
/**
125100
* Protocol of the HTTP / HTTPS server
126101
*/
127-
public get protocol(): HttpProtocol {
128-
return this._protocol;
102+
public get protocol(): string {
103+
return this._urlScheme || this._protocol;
129104
}
130105

131106
/**
@@ -147,13 +122,13 @@ export class HttpServer {
147122
*/
148123
public get url(): string {
149124
let host = this.host;
150-
if (this._address.family === 'IPv6') {
125+
if (this._address && this._address.family === 'IPv6') {
151126
if (host === '::') host = '::1';
152127
host = `[${host}]`;
153128
} else if (host === '0.0.0.0') {
154129
host = '127.0.0.1';
155130
}
156-
return `${this._protocol}://${host}:${this.port}`;
131+
return `${this.protocol}://${host}:${this.port}`;
157132
}
158133

159134
/**
@@ -163,10 +138,45 @@ export class HttpServer {
163138
return this._listening;
164139
}
165140

141+
public get server(): http.Server | https.Server {
142+
return this._server;
143+
}
144+
166145
/**
167146
* Address of the HTTP / HTTPS server
168147
*/
169148
public get address(): AddressInfo | undefined {
170149
return this._listening ? this._address : undefined;
171150
}
172151
}
152+
153+
export class HttpProtocolServerFactory implements ProtocolServerFactory {
154+
/**
155+
* Supports http and https
156+
* @param protocol
157+
*/
158+
supports(protocol: string) {
159+
return protocol === 'http' || protocol === 'https';
160+
}
161+
162+
/**
163+
* Create a server for the given protocol
164+
*/
165+
createServer(
166+
protocol: string,
167+
requestListener: RequestListener,
168+
serverOptions: HttpServerOptions,
169+
) {
170+
if (protocol === 'https') {
171+
const server = https.createServer(
172+
serverOptions as https.ServerOptions,
173+
requestListener,
174+
);
175+
return {server, urlScheme: protocol};
176+
} else if (protocol === 'http') {
177+
const server = http.createServer(requestListener);
178+
return {server, urlScheme: protocol};
179+
}
180+
throw new Error(`The ${protocol} protocol is not supported`);
181+
}
182+
}

packages/http-server/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1+
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
2+
// Node module: @loopback/http-server
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
export * from './types';
17
export * from './http-server';

packages/http-server/src/types.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import {AddressInfo} from 'net';
2+
import * as http from 'http';
3+
import * as https from 'https';
4+
import {IncomingMessage, ServerResponse} from 'http';
5+
6+
// Copyright IBM Corp. 2018. All Rights Reserved.
7+
// Node module: @loopback/http-server
8+
// This file is licensed under the MIT License.
9+
// License text available at https://opensource.org/licenses/MIT
10+
11+
export type RequestListener = (
12+
req: IncomingMessage,
13+
res: ServerResponse,
14+
) => void;
15+
16+
/**
17+
* Basic HTTP server listener options
18+
*
19+
* @export
20+
* @interface ListenerOptions
21+
*/
22+
export interface ListenerOptions {
23+
host?: string;
24+
port?: number;
25+
}
26+
27+
/**
28+
* HTTP server options
29+
*
30+
* @export
31+
* @interface HttpOptions
32+
*/
33+
export interface HttpOptions extends ListenerOptions {
34+
protocol?: 'http';
35+
}
36+
37+
/**
38+
* HTTPS server options
39+
*
40+
* @export
41+
* @interface HttpsOptions
42+
*/
43+
export interface HttpsOptions extends ListenerOptions, https.ServerOptions {
44+
protocol: 'https';
45+
}
46+
47+
/**
48+
* HTTP/2 server options
49+
*
50+
* @export
51+
* @interface Http2Options
52+
*/
53+
export interface Http2Options extends ListenerOptions {
54+
protocol: 'http2';
55+
// Other options for a module like https://github.com/spdy-http2/node-spdy
56+
[name: string]: unknown;
57+
}
58+
59+
/**
60+
* Possible server options
61+
*
62+
* @export
63+
* @type HttpServerOptions
64+
*/
65+
export type HttpServerOptions = HttpOptions | HttpsOptions | Http2Options;
66+
67+
/**
68+
* Supported protocols
69+
*
70+
* @export
71+
* @type HttpProtocol
72+
*/
73+
export type HttpProtocol = 'http' | 'https' | 'http2';
74+
75+
/**
76+
* HTTP / HTTPS server used by LoopBack's RestServer
77+
*
78+
* @export
79+
* @class HttpServer
80+
*/
81+
export interface HttpServer {
82+
readonly server: http.Server | https.Server;
83+
readonly protocol: string;
84+
readonly port: number;
85+
readonly host: string | undefined;
86+
readonly url: string;
87+
readonly listening: boolean;
88+
readonly address: AddressInfo | undefined;
89+
90+
start(): Promise<void>;
91+
stop(): Promise<void>;
92+
}
93+
94+
export interface ProtocolServer {
95+
server: http.Server | https.Server;
96+
/**
97+
* Scheme for the URL
98+
*/
99+
urlScheme: string;
100+
}
101+
102+
export interface ProtocolServerFactory {
103+
supports(protocol: string, serverOptions: HttpServerOptions): boolean;
104+
105+
createServer(
106+
protocol: string,
107+
requestListener: RequestListener,
108+
serverOptions: HttpServerOptions,
109+
): ProtocolServer;
110+
}

0 commit comments

Comments
 (0)