Skip to content

Commit

Permalink
feat(http-server): make http2 server creation extensible
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondfeng committed Nov 26, 2018
1 parent 86bfcbc commit b9cb3e0
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 120 deletions.
4 changes: 3 additions & 1 deletion packages/http-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
"@types/node": "^10.11.2",
"@types/p-event": "^1.3.0",
"@types/request-promise-native": "^1.0.15",
"request-promise-native": "^1.0.5"
"@types/spdy": "^3.4.4",
"request-promise-native": "^1.0.5",
"spdy": "^4.0.0"
},
"files": [
"README.md",
Expand Down
153 changes: 78 additions & 75 deletions packages/http-server/src/http-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,80 +3,32 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {IncomingMessage, ServerResponse} from 'http';
import * as http from 'http';
import * as https from 'https';
import {AddressInfo} from 'net';

import * as pEvent from 'p-event';

export type RequestListener = (
req: IncomingMessage,
res: ServerResponse,
) => void;

/**
* Basic HTTP server listener options
*
* @export
* @interface ListenerOptions
*/
export interface ListenerOptions {
host?: string;
port?: number;
}

/**
* HTTP server options
*
* @export
* @interface HttpOptions
*/
export interface HttpOptions extends ListenerOptions {
protocol?: 'http';
}

/**
* HTTPS server options
*
* @export
* @interface HttpsOptions
*/
export interface HttpsOptions extends ListenerOptions, https.ServerOptions {
protocol: 'https';
}

/**
* Possible server options
*
* @export
* @type HttpServerOptions
*/
export type HttpServerOptions = HttpOptions | HttpsOptions;

/**
* Supported protocols
*
* @export
* @type HttpProtocol
*/
export type HttpProtocol = 'http' | 'https'; // Will be extended to `http2` in the future
import {
HttpProtocol,
HttpServer,
HttpServerOptions,
RequestListener,
} from './types';

/**
* HTTP / HTTPS server used by LoopBack's RestServer
*
* @export
* @class HttpServer
*/
export class HttpServer {
export class DefaultHttpServer implements HttpServer {
private _port: number;
private _host?: string;
private _listening: boolean = false;
private _protocol: HttpProtocol;
protected _protocol: HttpProtocol;
private _address: AddressInfo;
private requestListener: RequestListener;
readonly server: http.Server | https.Server;
private serverOptions?: HttpServerOptions;
protected readonly requestListener: RequestListener;
protected _server: http.Server | https.Server;
protected readonly serverOptions: HttpServerOptions;

/**
* @param requestListener
Expand All @@ -87,37 +39,72 @@ export class HttpServer {
serverOptions?: HttpServerOptions,
) {
this.requestListener = requestListener;
serverOptions = serverOptions || {};
this.serverOptions = serverOptions;
this._port = serverOptions ? serverOptions.port || 0 : 0;
this._host = serverOptions ? serverOptions.host : undefined;
this._protocol = serverOptions ? serverOptions.protocol || 'http' : 'http';
this._port = serverOptions.port || 0;
this._host = serverOptions.host || undefined;
this._protocol = serverOptions.protocol || 'http';
this.createServer();
}

/**
* Create a server for the given protocol
*/
protected createServer() {
if (this._protocol === 'https') {
this.server = https.createServer(
this.serverOptions as https.ServerOptions,
this.requestListener,
);
this.createHttps();
} else if (this._protocol === 'http2') {
this.createHttp2();
} else {
this.server = http.createServer(this.requestListener);
this.createHttp();
}
}

/**
* Create an https server
*/
protected createHttps() {
this._server = https.createServer(
this.serverOptions as https.ServerOptions,
this.requestListener,
);
}

/**
* Create an http server
*/
protected createHttp() {
this._server = http.createServer(this.requestListener);
}

/**
* Create an http/2 server
*
* This method is to be implemented by a subclass
*/
protected createHttp2() {
// We cannot use the `http2` from node core yet until
// https://github.com/expressjs/express/pull/3730 is landed and released
throw new Error('HTTP/2 is not implemented.');
}

/**
* Starts the HTTP / HTTPS server
*/
public async start() {
this.server.listen(this._port, this._host);
await pEvent(this.server, 'listening');
this._server.listen(this._port, this._host);
await pEvent(this._server, 'listening');
this._listening = true;
this._address = this.server.address() as AddressInfo;
this._address = this._server.address() as AddressInfo;
}

/**
* Stops the HTTP / HTTPS server
*/
public async stop() {
if (!this.server) return;
this.server.close();
await pEvent(this.server, 'close');
if (!this._server) return;
this._server.close();
await pEvent(this._server, 'close');
this._listening = false;
}

Expand Down Expand Up @@ -147,13 +134,13 @@ export class HttpServer {
*/
public get url(): string {
let host = this.host;
if (this._address.family === 'IPv6') {
if (this._address && this._address.family === 'IPv6') {
if (host === '::') host = '::1';
host = `[${host}]`;
} else if (host === '0.0.0.0') {
host = '127.0.0.1';
}
return `${this._protocol}://${host}:${this.port}`;
return `${this.protocol}://${host}:${this.port}`;
}

/**
Expand All @@ -163,10 +150,26 @@ export class HttpServer {
return this._listening;
}

public get server(): http.Server | https.Server {
return this._server;
}

/**
* Address of the HTTP / HTTPS server
*/
public get address(): AddressInfo | undefined {
return this._listening ? this._address : undefined;
}
}

/**
* Default implementation of HttpServerFactory
*/
export class DefaultHttpServerFactory {
create(
requestListener: RequestListener,
serverOptions?: HttpServerOptions,
): HttpServer {
return new DefaultHttpServer(requestListener, serverOptions);
}
}
6 changes: 6 additions & 0 deletions packages/http-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
// Node module: @loopback/http-server
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export * from './types';
export * from './http-server';
102 changes: 102 additions & 0 deletions packages/http-server/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {AddressInfo} from 'net';
import * as http from 'http';
import * as https from 'https';
import {IncomingMessage, ServerResponse} from 'http';

// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/http-server
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export type RequestListener = (
req: IncomingMessage,
res: ServerResponse,
) => void;

/**
* Basic HTTP server listener options
*
* @export
* @interface ListenerOptions
*/
export interface ListenerOptions {
host?: string;
port?: number;
}

/**
* HTTP server options
*
* @export
* @interface HttpOptions
*/
export interface HttpOptions extends ListenerOptions {
protocol?: 'http';
}

/**
* HTTPS server options
*
* @export
* @interface HttpsOptions
*/
export interface HttpsOptions extends ListenerOptions, https.ServerOptions {
protocol: 'https';
}

/**
* HTTP/2 server options
*
* @export
* @interface Http2Options
*/
export interface Http2Options extends ListenerOptions {
protocol: 'http2';
// Other options for a module like https://github.com/spdy-http2/node-spdy
[name: string]: unknown;
}

/**
* Possible server options
*
* @export
* @type HttpServerOptions
*/
export type HttpServerOptions = HttpOptions | HttpsOptions | Http2Options;

/**
* Supported protocols
*
* @export
* @type HttpProtocol
*/
export type HttpProtocol = 'http' | 'https' | 'http2';

/**
* HTTP / HTTPS server used by LoopBack's RestServer
*
* @export
* @class HttpServer
*/
export interface HttpServer {
readonly server: http.Server | https.Server;
readonly protocol: HttpProtocol;
readonly port: number;
readonly host: string | undefined;
readonly url: string;
readonly listening: boolean;
readonly address: AddressInfo | undefined;

start(): Promise<void>;
stop(): Promise<void>;
}

/**
* A factory to create http servers
*/
export interface HttpServerFactory {
create(
requestListener: RequestListener,
serverOptions?: HttpServerOptions,
): HttpServer;
}
Loading

0 comments on commit b9cb3e0

Please sign in to comment.