@ajejoseph22/proxx
Version:
A lightweight HTTPS/HTTP proxy server with bandwidth tracking, basic auth and real-time analytics.
162 lines (161 loc) • 7.57 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Proxx = void 0;
const express_1 = __importDefault(require("express"));
const http_proxy_middleware_1 = require("http-proxy-middleware");
const http_1 = __importDefault(require("http"));
const net_1 = __importDefault(require("net"));
const url_1 = require("url");
const metrics_service_1 = require("./services/metrics-service");
const auth_service_1 = require("./services/auth-service");
const database_service_1 = require("./services/database-service");
class Proxx {
constructor(port) {
this.port = port;
this.app = (0, express_1.default)();
this.server = http_1.default.createServer(this.app);
this.dbService = new database_service_1.DatabaseService();
this.metricsService = new metrics_service_1.MetricsService(this.dbService);
this.authService = new auth_service_1.AuthService(this.dbService);
this.endpointPaths = ["/metrics"];
this.setupMiddleware();
this.setupEndpoints();
this.setupProxy();
}
isProxyRequest(req) {
return !this.endpointPaths.includes(req.url);
}
setupMiddleware() {
this.app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
const { isAuthenticated, message, code } = this.isProxyRequest(req)
? yield this.authService.proxyAuth(req, res)
: yield this.authService.endpointAuth(req, res);
if (!isAuthenticated) {
res.status(code).send(message);
return;
}
next();
}));
}
setupProxy() {
this.app.use("/", (0, http_proxy_middleware_1.createProxyMiddleware)({
target: "https://example.com",
router: (req) => {
console.log(`Proxying request to: ${req.url}`);
return req.url;
},
changeOrigin: true,
on: {
proxyReq: (_, req, res) => {
let requestBytes = 0;
req.on("data", (chunk) => {
requestBytes += chunk.length;
});
req.on("end", () => __awaiter(this, void 0, void 0, function* () {
req.requestBytes = requestBytes;
}));
},
proxyRes: (proxyRes, req, res) => {
const url = new url_1.URL(req.url || "", `http://${req.headers.host}`)
.hostname;
let responseBytes = 0;
proxyRes.on("data", (chunk) => {
responseBytes += chunk.length;
});
proxyRes.on("end", () => __awaiter(this, void 0, void 0, function* () {
console.log("Request bytes:", req.requestBytes);
console.log("Response bytes:", responseBytes);
const totalBytes = (req.requestBytes || 0) + responseBytes;
yield this.metricsService.updateMetrics(url, totalBytes);
}));
},
},
}));
}
setupEndpoints() {
this.app.get("/metrics", (_, res) => {
const metrics = this.metricsService.getAllMetrics();
res.json(metrics);
});
}
start() {
return __awaiter(this, void 0, void 0, function* () {
yield this.dbService.initialize();
yield this.authService.initialize();
this.server = this.app.listen(this.port, () => {
console.log(`Proxx is running on port ${this.port}`);
});
// Handle HTTPS tunneling (CONNECT requests)
this.server.on("connect", (req, clientSocket, head) => __awaiter(this, void 0, void 0, function* () {
const { isAuthenticated, message, code } = yield this.authService.proxyAuth(req);
if (!isAuthenticated) {
clientSocket.write(`HTTP/1.1 ${code} ${message}`);
clientSocket.end();
return;
}
if (!req.url) {
console.error("Invalid CONNECT request:", req.url);
clientSocket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
return;
}
const [hostname, port] = req.url.split(":");
if (!hostname || !port) {
console.error("Invalid CONNECT request:", req.url);
clientSocket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
return;
}
// Establish TCP connection to the target server
const serverSocket = net_1.default.connect(Number(port), hostname, () => {
console.log(`Tunnel established to ${hostname}:${port}`);
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
serverSocket.write(head);
serverSocket.pipe(clientSocket);
clientSocket.pipe(serverSocket);
});
let clientBytes = 0;
let serverBytes = 0;
clientSocket.on("data", (chunk) => {
clientBytes += chunk.length;
});
serverSocket.on("data", (chunk) => {
serverBytes += chunk.length;
});
clientSocket.on("end", () => __awaiter(this, void 0, void 0, function* () {
console.log("Client bytes:", clientBytes);
console.log("Server bytes:", serverBytes);
const totalBytes = clientBytes + serverBytes;
yield this.metricsService.updateMetrics(hostname, totalBytes);
}));
serverSocket.on("error", (err) => {
console.error(`Error in tunnel to ${hostname}:${port} - ${err.message}`);
clientSocket.end("HTTP/1.1 500 Internal Server Error\r\n\r\n");
});
clientSocket.on("error", (err) => {
console.error(`Client socket error: ${err.message}`);
});
}));
});
}
stop() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
console.log("Gracefully shutting down Proxx...");
const metrics = this.metricsService.getAllMetrics();
console.log("Total Metrics:", JSON.stringify(metrics, null, 2));
(_a = this.server) === null || _a === void 0 ? void 0 : _a.close();
});
}
}
exports.Proxx = Proxx;