ksmf
Version:
Modular Microframework for create minimalistic CLI/Web application or REST API
204 lines (190 loc) • 6.61 kB
JavaScript
/**
* @author Antonio Membrides Espinosa
* @email tonykssa@gmail.com
* @date 15/11/2021
* @copyright Copyright (c) 2020-2030
* @license GPL
* @version 1.0
**/
const http = require('http');
const net = require('net')
const url = require('url')
const AppWEB = require('./WEB');
const HttpHandler = require('../common/Http');
class ProxyApp {
/**
* @type {Object|null}
*/
helper;
/**
* @type {Console|null}
*/
logger;
constructor(path) {
this.app = new AppWEB(path)
}
/**
* @description initialize server proxy app
*/
init() {
try {
this.app.init();
if (!this.app.helper.get('http')) {
this.app.helper.set(new HttpHandler(), 'http');
}
} catch (error) {
this.app.setError(error);
}
return this;
}
/**
* @description start app server
*/
start() {
this.run();
}
/**
* @description stop app server
*/
stop() {
this.app.stop();
}
/**
* @description run app server
*/
run() {
if (!this.app.web) {
this.init();
}
const server = http.createServer(this.app.web);
const listener = server.listen(this.app.cfg.srv.port, (err) => {
if (err) {
return this.app.setError(err);
}
const info = /** @type {{ address: string; family: string; port: number; }} */ listener.address();
this.app?.emit('onStart', [{ server: 'on', host: typeof info === "object" && info.address, port: typeof info === "object" && info.port }, this]);
});
server.on('connect', async (req, res, head) => {
//... Only for HTTP/1.1 CONNECT method
this.inf = this.initInfo(req, res, head);
this.app.emit('onConnectStart', [req, res, this.inf]);
this.inf.status = this.inf.status && await this.initAuth(req, res);
this.inf.status = this.inf.status && await this.initRules(req, res);
this.app?.emit('onConnectEnd', [req, res, { state: this.inf.status ? 'ALLOW' : 'DENY', url: this.inf.destination.url, ...this.inf.origin }, this]);
if (this.inf.status) {
this.pipe(req, res, this);
}
});
return listener;
}
/**
* @description Get request information
* @param {Object} req
* @param {Object} res
* @param {Object} head
* @returns {Object}
*/
initInfo(req, res, head) {
const to = url.parse(`//${req.url}`, false, true);
const token = req.headers['proxy-authorization'];
return {
status: true,
method: req.method,
origin: {
token,
user: { username: 'anonymous' },
agent: req.headers['user-agent'],
host: res.remoteAddress,
port: res.remotePort
},
destination: {
url: req.url,
...to
},
security: this.app.cfg.srv.security
};
}
/**
* @description Run rules handler and get if is valid request or not
* @param {Object} req
* @param {Object} res
* @returns {Promise<boolean>}
*/
async initRules(req, res) {
const srvRules = this.app.helper.get('rule');
if (!(srvRules?.verify instanceof Function)) {
return true;
}
const verification = await srvRules.verify(req, res, this.inf);
if (!verification) {
try {
this.app.helper.get('http').send(res, 403);
}
catch (error) {
this.app.setError(error);
}
return false;
}
return true;
}
/**
* @description Run authentication handler and get if is valid request or not
* @param {Object} req
* @param {Object} res
* @returns {Promise<boolean>}
*/
async initAuth(req, res) {
const srvAuth = this.app.helper.get('auth');
if (!(srvAuth?.verify instanceof Function)) {
return true;
}
res.user = await srvAuth.verify(req, res, this.inf);
if (!res.user) {
try {
this.app.helper.get('http').send(res, 407);
}
catch (error) {
this.app.setError(error);
}
return false;
}
this.inf.origin.user = res.user;
return true;
}
pipe(req, clientSocket, srv) {
const { port, hostname } = srv.inf.destination;
if (hostname && port) {
const serverSocket = net.connect(port, hostname);
srv.app.emit('onPipe', 'ksmf', [req, clientSocket, serverSocket]);
const serverErrorHandler = (err) => {
srv.app.setError(err, req, clientSocket);
srv.app.helper.get('http').send(clientSocket, 'custom', err.message);
}
const serverEndHandler = () => {
srv.app.setError('External Server End', req, clientSocket);
srv.app.helper.get('http').send(clientSocket, 500);
}
const clientErrorHandler = (err) => {
srv.app.setError(err, req, clientSocket);
srv.app.helper.get('http').send(clientSocket, 'custom', err.message);
}
const clientEndHandler = () => {
srv.app.setError('client End Handler', req, clientSocket);
srv.app.helper.get('http').send(clientSocket, 'custom', 'client_end');
}
clientSocket.on('error', clientErrorHandler);
clientSocket.on('end', clientEndHandler);
serverSocket.on('error', serverErrorHandler);
serverSocket.on('end', serverEndHandler);
serverSocket.on('connect', () => {
srv.app.helper.get('http').send(clientSocket, 200);
serverSocket.pipe(clientSocket, { end: false });
clientSocket.pipe(serverSocket, { end: false });
})
} else {
srv.app.setError('Bad Request', req, clientSocket);
srv.app.helper.get('http').send(clientSocket, 400);
}
}
}
module.exports = ProxyApp;