pinusmod
Version:
[](https://travis-ci.org/node-pinus/pinus)
143 lines (119 loc) • 4.24 kB
text/typescript
import { EventEmitter } from 'events';
import * as util from 'util';
import { WSProcessor } from './wsprocessor';
import { TCPProcessor } from './tcpprocessor';
import { getLogger } from 'pinusmod-logger';
import * as net from 'net';
import * as tls from 'tls';
import { TlsOptions } from 'tls';
import * as WebSocket from 'ws';
import { TcpSocket } from './tcpsocket';
import { IHybridSocket } from './IHybridSocket';
import * as path from 'path';
let logger = getLogger('pinus', path.basename(__filename));
let HTTP_METHODS = [
'GET', 'POST', 'DELETE', 'PUT', 'HEAD'
];
// max length of 'DELETE'
let MAX_HTTP_METHODS_LEN = 6;
let ST_STARTED = 1;
let ST_CLOSED = 2;
let DEFAULT_TIMEOUT = 90;
export type SslWatcher = (cb: (opts: tls.SecureContextOptions) => void) => void;
export interface HybridSwitcherOptions {
closeMethod ?: 'end';
timeout ?: number;
setNoDelay ?: boolean;
ssl ?: TlsOptions;
sslWatcher?: SslWatcher;
}
export interface IHybridSwitcher {
on(evt: 'connection' , listener: (socket: IHybridSocket) => void): void;
}
/**
* Switcher for tcp and websocket protocol
*
* @param {Object} server tcp server instance from node.js net module
*/
export class HybridSwitcher extends EventEmitter implements IHybridSwitcher {
server: net.Server;
wsprocessor: WSProcessor;
tcpprocessor: TCPProcessor;
id: number;
timeout: number;
setNoDelay: boolean;
state: number;
constructor(server: net.Server, opts: HybridSwitcherOptions) {
super();
this.server = server;
this.wsprocessor = new WSProcessor();
this.tcpprocessor = new TCPProcessor(opts.closeMethod);
this.id = 1;
this.timeout = (opts.timeout || DEFAULT_TIMEOUT) * 1000;
this.setNoDelay = opts.setNoDelay;
if (!opts.ssl) {
this.server.on('connection', this.newSocket.bind(this));
} else {
this.server.on('secureConnection', this.newSocket.bind(this));
this.server.on('clientError', function (e, tlsSo) {
logger.warn('an ssl error occured before handshake established: ', e);
tlsSo.destroy();
});
}
this.wsprocessor.on('connection', this.emit.bind(this, 'connection'));
this.tcpprocessor.on('connection', this.emit.bind(this, 'connection'));
this.state = ST_STARTED;
}
newSocket(socket: net.Socket) {
if (this.state !== ST_STARTED) {
return;
}
socket.on('error', (err: Error) => {
logger.debug('connection error:%s, the remote ip is %s && port is %s', err.message, socket.remoteAddress, socket.remotePort);
socket.destroy();
});
socket.on('close', () => {
socket.destroy();
});
socket.setTimeout(this.timeout, function () {
logger.warn('connection is timeout without communication, the remote ip is %s && port is %s',
socket.remoteAddress, socket.remotePort);
socket.destroy();
});
let self = this;
socket.once('data', (data) => {
// FIXME: handle incomplete HTTP method
if (isHttp(data)) {
this.processHttp(self.wsprocessor, socket, data);
} else {
if (!!self.setNoDelay) {
socket.setNoDelay(true);
}
this.processTcp(self.tcpprocessor, socket, data);
}
});
}
close() {
if (this.state !== ST_STARTED) {
return;
}
this.state = ST_CLOSED;
this.wsprocessor.close();
this.tcpprocessor.close();
}
private processHttp(processor: WSProcessor, socket: net.Socket, data: Buffer) {
processor.add(socket, data);
}
private processTcp(processor: TCPProcessor, socket: net.Socket, data: Buffer) {
processor.add(socket, data);
}
}
let isHttp = function (data: Buffer) {
let head = data.toString('utf8', 0, MAX_HTTP_METHODS_LEN - 1);
for (let i = 0, l = HTTP_METHODS.length; i < l; i++) {
if (head.indexOf(HTTP_METHODS[i]) === 0) {
return true;
}
}
return false;
};