Refacted Server Implementation

This commit is contained in:
Xiaoning Liu 2019-03-05 12:18:15 +08:00 коммит произвёл XiaoningLiu
Родитель 3d0c24cc3a
Коммит 33c9525743
7 изменённых файлов: 190 добавлений и 120 удалений

13
.gitignore поставляемый
Просмотреть файл

@ -1,7 +1,8 @@
.nyc_output
node_modules/
coverage/
dist
typings/
node_modules
coverage
azurite-testdrive
__blobstorage__
__blobpersistence__
__azurite_db_blob__.json
.nyc_output
dist
typings

66
src/blob/BlobServer.ts Normal file
Просмотреть файл

@ -0,0 +1,66 @@
import * as http from "http";
import Server from "../common/IServer";
import getAPP from "./app";
import Configuration from "./Configuration";
import { IDataStore } from "./persistence/IDataStore";
import LokiBlobDataStore from "./persistence/LokiBlobDataStore";
import { DEFAULT_BLOB_PERSISTENCE_PATH, DEFAULT_LOKI_DB_PATH } from "./utils/constants";
import logger from "./utils/log/Logger";
// Decouple server & app layer
// Server layer should only care about sever related configurations, like listening port etc.
// App layer will handle middleware related things
/**
* Azurite HTTP server.
*
* @export
* @class Server
*/
export default class BlobServer extends Server {
private readonly dataStore: IDataStore;
/**
* Creates an instance of Server.
*
* @param {Configuration} configuration
* @memberof Server
*/
constructor(configuration: Configuration) {
const host = configuration.host;
const port = configuration.port;
const dataStore = new LokiBlobDataStore(
configuration.dbPath || DEFAULT_LOKI_DB_PATH,
configuration.persistencePath || DEFAULT_BLOB_PERSISTENCE_PATH
);
const httpServer = http.createServer(getAPP(dataStore));
super(host, port, httpServer);
this.dataStore = dataStore;
}
protected async beforeStart(): Promise<void> {
await this.dataStore.init();
}
protected async afterStart(): Promise<void> {
let address = this.httpServer.address();
if (typeof address !== "string") {
address = address.address;
}
logger.info(
`Azurite Blob service successfully listens on ${address}:${this.port}`
);
}
protected async beforeClose(): Promise<void> {
logger.info(
`Azurite Blob service is shutdown... Waiting for existing keep-alive connections timeout...`
);
}
protected async afterClose(): Promise<void> {
await this.dataStore.close();
}
}

Просмотреть файл

@ -1,100 +0,0 @@
import * as http from "http";
import getAPP from "./app";
import Configuration from "./Configuration";
import { IDataStore } from "./persistence/IDataStore";
import LokiBlobDataStore from "./persistence/LokiBlobDataStore";
import { DEFAULT_BLOB_PERSISTENCE_PATH, DEFAULT_LOKI_DB_PATH } from "./utils/constants";
import logger from "./utils/log/Logger";
// Decouple server & app layer
// Server layer should only care about sever related configurations, like listening port etc.
// App layer will handle middleware related things
/**
* Azurite HTTP server.
*
* @export
* @class Server
*/
export default class Server {
public readonly port: number;
public readonly host: string;
private readonly dataStore: IDataStore;
private readonly httpServer: http.Server;
/**
* Creates an instance of Server.
*
* @param {Configuration} configuration
* @memberof Server
*/
constructor(configuration: Configuration) {
this.host = configuration.host;
this.port = configuration.port;
const dataStore = (this.dataStore = new LokiBlobDataStore(
configuration.dbPath || DEFAULT_LOKI_DB_PATH,
configuration.persistencePath || DEFAULT_BLOB_PERSISTENCE_PATH
));
this.httpServer = http.createServer(getAPP(dataStore));
}
/**
* Initialize resources server needed, such as database connections and other resources.
*
* @returns {Promise<void>}
* @memberof Server
*/
public async init(): Promise<void> {
await this.dataStore.init();
}
/**
* Starts HTTP server.
*
* @returns {Promise<void>}
* @memberof Server
*/
public async start(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.httpServer
.listen(this.port, this.host, () => {
let address = this.httpServer.address();
if (typeof address !== "string") {
address = address.address;
}
logger.info(
`Azurite Blob service successfully listens on ${address}:${
this.port
}`
);
resolve();
})
.on("error", reject);
});
}
/**
* Close HTTP server, database connections or other resources.
*
* @returns {Promise<void>}
* @memberof Server
*/
public async close(): Promise<void> {
logger.info(`Azurite Blob service is shutdown... Waiting for existing keep-alive connections timeout...`);
// Close HTTP server first to deny incoming connections
// You will find this will not close server immediately because there maybe existing keep-alive connections
// Calling httpServer.close will only stop accepting incoming requests
// and wait for existing keep-alive connections timeout
// Default keep-alive timeout is 5 seconds defined by httpServer.keepAliveTimeout
// TODO: Add a middleware to reject incoming request over existing keep-alive connections
// https://github.com/nodejs/node/issues/2642
await new Promise((resolve) => {
this.httpServer.close(resolve);
});
await this.dataStore.close();
}
}

Просмотреть файл

@ -1,10 +1,10 @@
import Server from "../common/IServer";
import BlobServer from "./BlobServer";
import Configuration from "./Configuration";
import Server from "./Server";
async function main() {
const config = new Configuration();
const server = new Server(config);
await server.init();
const server: Server = new BlobServer(config);
await server.start();
process.on("message", (msg) => {

Просмотреть файл

@ -2,9 +2,9 @@ export const DEFAULT_SERVER_HOST_NAME = "127.0.0.1";
export const DEFAULT_SERVER_LISTENING_PORT = 10000;
export const DEFAULT_LOKI_DB_PATH = "__blobstorage__";
export const DEFAULT_LOKI_DB_PATH = "__azurite_db_blob__.json";
export const DEFAULT_BLOB_PERSISTENCE_PATH = "__blobpersistence__";
export const DEFAULT_BLOB_PERSISTENCE_PATH = "__blobstorage__";
export const DEFAULT_CONTEXT_PATH = "azurite_blob_context";

110
src/common/IServer.ts Normal file
Просмотреть файл

@ -0,0 +1,110 @@
import * as http from "http";
/**
* Abstract Server class for Azurite Servers.
*
* @export
* @abstract
* @class Server
*/
export default abstract class Server {
/**
* Creates an instance of Server.
*
* @param {string} host
* @param {number} port
* @param {http.Server} httpServer
* @memberof Server
*/
public constructor(
public readonly host: string,
public readonly port: number,
protected readonly httpServer: http.Server
) {}
/**
* Initialize and start the server to service incoming HTTP requests.
*
* @abstract
* @returns {Promise<void>}
* @memberof Server
*/
public async start(): Promise<void> {
await this.beforeStart();
await new Promise<void>((resolve, reject) => {
this.httpServer
.listen(this.port, this.host, resolve)
.on("error", reject);
});
await this.afterStart();
}
/**
* Dispose HTTP server and clean up other resources.
*
* We name this method as close instead of dispose, because in practices, usually we cannot re-open the resources
* disposed, but can re-open the resources closed.
*
* @abstract
* @returns {Promise<void>}
* @memberof Server
*/
public async close(): Promise<void> {
await this.beforeClose();
// Close HTTP server first to deny incoming connections
// You will find this will not close server immediately because there maybe existing keep-alive connections
// Calling httpServer.close will only stop accepting incoming requests
// and wait for existing keep-alive connections timeout
// Default keep-alive timeout is 5 seconds defined by httpServer.keepAliveTimeout
// TODO: Add a middleware to reject incoming request over existing keep-alive connections
// https://github.com/nodejs/node/issues/2642
await new Promise(resolve => {
this.httpServer.close(resolve);
});
await this.afterClose();
}
/**
* Async task before server starts.
*
* @protected
* @abstract
* @returns {Promise<void>}
* @memberof Server
*/
protected abstract async beforeStart(): Promise<void>;
/**
* Async task after server starts.
*
* @protected
* @abstract
* @returns {Promise<void>}
* @memberof Server
*/
protected abstract async afterStart(): Promise<void>;
/**
* Async task before server closes.
*
* @protected
* @abstract
* @returns {Promise<void>}
* @memberof Server
*/
protected abstract async beforeClose(): Promise<void>;
/**
* Async task after server closes.
*
* @protected
* @abstract
* @returns {Promise<void>}
* @memberof Server
*/
protected abstract async afterClose(): Promise<void>;
}

Просмотреть файл

@ -1,15 +1,9 @@
import { Aborter, AnonymousCredential, ServiceURL, StorageURL } from "@azure/storage-blob";
import * as assert from "assert";
import { rmdirSync, unlinkSync } from "fs";
import {
Aborter,
AnonymousCredential,
ServiceURL,
StorageURL,
} from "@azure/storage-blob";
import Server from "../../../src/blob/BlobServer";
import Configuration from "../../../src/blob/Configuration";
import Server from "../../../src/blob/Server";
// TODO: Create a server factory as tests utils
const host = "127.0.0.1";
@ -32,7 +26,6 @@ let server: Server;
describe("ServiceHandler", () => {
before(async () => {
server = new Server(config);
await server.init();
await server.start();
});