Skip to content

Commit

Permalink
feat: https server configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
drauggres committed Oct 5, 2021
1 parent 5995d7a commit 6a61ecc
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 80 deletions.
2 changes: 1 addition & 1 deletion src/app/client/HostTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class HostTracker extends ManagerClient<ParamsBase, HostTrackerEvents> {
// this.emit('hosts', msg.data);
if (msg.data.local) {
msg.data.local.forEach(({ type }) => {
const secure = location.protocol === 'https';
const secure = location.protocol === 'https:';
const port = location.port ? parseInt(location.port, 10) : secure ? 443 : 80;
const { hostname } = location;
if (type !== 'android' && type !== 'ios') {
Expand Down
35 changes: 23 additions & 12 deletions src/server/Config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as process from 'process';
import * as fs from 'fs';
import * as path from 'path';
import { Configuration, HostItem } from '../types/Configuration';
import { Configuration, HostItem, ServerItem } from '../types/Configuration';
import { EnvName } from './EnvName';

const DEFAULT_PORT = 8000;

export class Config {
private static instance?: Config;
public static getInstance(defaultConfig?: Configuration): Config {
Expand Down Expand Up @@ -35,19 +37,16 @@ export class Config {
if (!configPath) {
return;
}
const isAbsolute = configPath.startsWith('/');
const absolutePath = isAbsolute ? configPath : path.resolve(process.cwd(), configPath);
this.fullConfig = JSON.parse(this.readFile(configPath));
}

public readFile(pathString: string): string {
const isAbsolute = pathString.startsWith('/');
const absolutePath = isAbsolute ? pathString : path.resolve(process.cwd(), pathString);
if (!fs.existsSync(absolutePath)) {
console.error(`Can't find configuration file "${absolutePath}"`);
return;
}
try {
const configString = fs.readFileSync(absolutePath).toString();
this.fullConfig = JSON.parse(configString);
} catch (e) {
console.error(`Failed to load configuration from file "${absolutePath}"`);
console.error(`Error: ${e.message}`);
throw Error(`Can't find configuration file "${absolutePath}"`);
}
return fs.readFileSync(absolutePath).toString();
}

public getHostList(): HostItem[] {
Expand Down Expand Up @@ -78,4 +77,16 @@ export class Config {
}
return this.fullConfig.runApplTracker === true;
}

public getServers(): ServerItem[] {
if (!Array.isArray(this.fullConfig.server)) {
return [
{
secure: false,
port: DEFAULT_PORT,
},
];
}
return this.fullConfig.server;
}
}
59 changes: 32 additions & 27 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,35 +87,40 @@ async function loadApplModules() {
loadPlatformModulesPromises.push(loadApplModules());
/// #endif

Promise.all(loadPlatformModulesPromises).then(() => {
servicesToStart.forEach((serviceClass: ServiceClass) => {
const service = serviceClass.getInstance();
runningServices.push(service);
service.start();
Promise.all(loadPlatformModulesPromises)
.then(() => {
servicesToStart.forEach((serviceClass: ServiceClass) => {
const service = serviceClass.getInstance();
runningServices.push(service);
service.start();
});

const wsService = WebSocketServer.getInstance();
mwList.forEach((mwFactory: MwFactory) => {
wsService.registerMw(mwFactory);
});

mw2List.forEach((mwFactory: MwFactory) => {
WebsocketMultiplexer.registerMw(mwFactory);
});

if (process.platform === 'win32') {
readline
.createInterface({
input: process.stdin,
output: process.stdout,
})
.on('SIGINT', exit);
}

process.on('SIGINT', exit);
process.on('SIGTERM', exit);
})
.catch((error) => {
console.error(error.message);
exit('1');
});

const wsService = WebSocketServer.getInstance();
mwList.forEach((mwFactory: MwFactory) => {
wsService.registerMw(mwFactory);
});

mw2List.forEach((mwFactory: MwFactory) => {
WebsocketMultiplexer.registerMw(mwFactory);
});

if (process.platform === 'win32') {
readline
.createInterface({
input: process.stdin,
output: process.stdout,
})
.on('SIGINT', exit);
}

process.on('SIGINT', exit);
process.on('SIGTERM', exit);
});

let interrupted = false;
function exit(signal: string) {
console.log(`\nReceived signal ${signal}`);
Expand Down
70 changes: 49 additions & 21 deletions src/server/services/HttpServer.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import * as http from 'http';
import * as https from 'https';
import path from 'path';
import { Service } from './Service';
import { Utils } from '../Utils';
import express, { Express } from 'express';
import { Config } from '../Config';

const proto = 'http';
const DEFAULT_PORT = 8000;
const DEFAULT_STATIC_DIR = path.join(__dirname, './public');

export type ServerAndPort = {
server: https.Server | http.Server;
port: number;
};

export class HttpServer implements Service {
private static instance: HttpServer;
private static PORT = DEFAULT_PORT;
private static PUBLIC_DIR = DEFAULT_STATIC_DIR;
private static SERVE_STATIC = true;
private server?: http.Server;
private servers: ServerAndPort[] = [];
private app?: Express;

protected constructor() {
Expand All @@ -31,13 +35,6 @@ export class HttpServer implements Service {
return !!this.instance;
}

public static setPort(port: number): void {
if (HttpServer.instance) {
throw Error('Unable to change value after instantiation');
}
HttpServer.PORT = port;
}

public static setPublicDir(dir: string): void {
if (HttpServer.instance) {
throw Error('Unable to change value after instantiation');
Expand All @@ -52,29 +49,60 @@ export class HttpServer implements Service {
HttpServer.SERVE_STATIC = enabled;
}

public getPort(): number {
return HttpServer.PORT;
}

public getServer(): http.Server | undefined {
return this.server;
public getServers(): ServerAndPort[] {
return [...this.servers];
}

public getName(): string {
return `HTTP Server {tcp:${HttpServer.PORT}}`;
return `HTTP(s) Server Service`;
}

public start(): void {
this.app = express();
if (HttpServer.SERVE_STATIC && HttpServer.PUBLIC_DIR) {
this.app.use(express.static(HttpServer.PUBLIC_DIR));
}
this.server = http.createServer(this.app).listen(HttpServer.PORT, () => {
Utils.printListeningMsg(proto, HttpServer.PORT);
const config = Config.getInstance();
config.getServers().forEach((serverItem) => {
const { secure, port } = serverItem;
let proto: string;
let server: http.Server | https.Server;
if (secure) {
if (!serverItem.options) {
throw Error('Must provide option for secure server configuration');
}
let { key, cert } = serverItem.options;
const { keyPath, certPath } = serverItem.options;
if (!key) {
if (typeof keyPath !== 'string') {
throw Error('Must provide parameter "key" or "keyPath"');
}
key = config.readFile(keyPath);
}
if (!cert) {
if (typeof certPath !== 'string') {
throw Error('Must provide parameter "cert" or "certPath"');
}
cert = config.readFile(certPath);
}
const options = { ...serverItem.options, cert, key };
server = https.createServer(options, this.app);
proto = 'https';
} else {
const options = serverItem.options ? { ...serverItem.options } : {};
server = http.createServer(options, this.app);
proto = 'http';
}
this.servers.push({ server, port });
server.listen(port, () => {
Utils.printListeningMsg(proto, port);
});
});
}

public release(): void {
this.server?.close();
this.servers.forEach((item) => {
item.server.close();
});
}
}
38 changes: 19 additions & 19 deletions src/server/services/WebSocketServer.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import * as http from 'http';
import { Server as WSServer } from 'ws';
import WS from 'ws';
import querystring from 'querystring';
import url from 'url';
import { Service } from './Service';
import { HttpServer } from './HttpServer';
import { HttpServer, ServerAndPort } from './HttpServer';
import { MwFactory } from '../mw/Mw';

export class WebSocketServer implements Service {
private static instance?: WebSocketServer;
private server?: WSServer;
private port = 0;
private servers: WSServer[] = [];
private mwFactories: Set<MwFactory> = new Set();

protected constructor() {
Expand All @@ -32,11 +30,13 @@ export class WebSocketServer implements Service {
this.mwFactories.add(mwFactory);
}

public attachToServer(httpServer: http.Server): WSServer {
const wss = new WSServer({ server: httpServer });
public attachToServer(item: ServerAndPort): WSServer {
const { server, port } = item;
const TAG = `WebSocket Server {tcp:${port}}`;
const wss = new WSServer({ server });
wss.on('connection', async (ws: WS, request) => {
if (!request.url) {
ws.close(4001, `[${this.getName()}] Invalid url`);
ws.close(4001, `[${TAG}] Invalid url`);
return;
}
const parsedUrl = url.parse(request.url);
Expand All @@ -49,35 +49,35 @@ export class WebSocketServer implements Service {
}
}
if (!processed) {
ws.close(4002, `[${this.getName()}] Unsupported request`);
ws.close(4002, `[${TAG}] Unsupported request`);
}
return;
});
wss.on('close', () => {
console.log(`${this.getName()} stopped`);
console.log(`${TAG} stopped`);
});
this.server = wss;
this.servers.push(wss);
return wss;
}

public getServer(): WSServer | undefined {
return this.server;
public getServers(): WSServer[] {
return this.servers;
}

public getName(): string {
return `WebSocket Server {tcp:${this.port}}`;
return `WebSocket Server Service`;
}

public start(): void {
const service = HttpServer.getInstance();
const server = service.getServer();
this.port = service.getPort();
if (server) {
this.attachToServer(server);
}
service.getServers().forEach((item) => {
this.attachToServer(item);
});
}

public release(): void {
this.server?.close();
this.servers.forEach((server) => {
server.close();
});
}
}
14 changes: 14 additions & 0 deletions src/types/Configuration.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as https from 'https';

export interface HostItem {
type: 'android' | 'ios';
secure: boolean;
Expand All @@ -6,7 +8,19 @@ export interface HostItem {
useProxy?: boolean;
}

export type ExtendedServerOption = https.ServerOptions & {
certPath?: string;
keyPath?: string;
};

export interface ServerItem {
secure: boolean;
port: number;
options?: ExtendedServerOption;
}

export interface Configuration {
server?: ServerItem[];
runApplTracker?: boolean;
announceApplTracker?: boolean;
runGoogTracker?: boolean;
Expand Down

0 comments on commit 6a61ecc

Please sign in to comment.