@galaxyproject/gx-it-proxy
Version:
A dynamic reverse proxy for Interactive Tools in Galaxy
197 lines (182 loc) • 5.55 kB
JavaScript
const fs = require("fs");
const sqlite3 = require("sqlite3");
const watchFile = require("node-watch");
let postgresClient;
try {
postgresClient = require("pg-native");
} catch {
// pg-native not installed or cannot be loaded
postgresClient = null;
}
var updateFromJson = function (path, map) {
var content = fs.readFileSync(path, "utf8");
var keyToSession = JSON.parse(content);
var newSessions = {};
for (var key in keyToSession) {
let info = keyToSession[key]?.info;
if (info) {
info = JSON.parse(info);
}
newSessions[key] = {
target: {
host: keyToSession[key]["host"],
port: parseInt(keyToSession[key]["port"]),
requires_path_in_url: info?.requires_path_in_url,
requires_path_in_header_named: info?.requires_path_in_header_named,
},
};
}
for (var oldSession in map) {
if (!(oldSession in newSessions)) {
delete map[oldSession];
}
}
for (var newSession in newSessions) {
map[newSession] = newSessions[newSession];
}
};
/*
CREATE TABLE gxitproxy
(key text,
key_type text,
token text,
host text,
port integer,
info text,
PRIMARY KEY (key, key_type)
);
INSERT INTO "gxitproxy" VALUES('e3c1915314cbd610','interactivetoolentrypoint','d76ec842e77049059753b0d40992d15f','132.230.68.22',1033,NULL);
INSERT INTO "gxitproxy" VALUES('d24902ddec2e97f1','interactivetoolentrypoint','bea3a5bb3d4b4c6c8bcb7579e023bf66','132.230.68.22',1035,NULL);
*/
var updateFromSqlite = function (path, map) {
var newSessions = {};
var loadSessions = function () {
db.each(
"SELECT key, key_type, token, host, port, info FROM gxitproxy",
function (_err, row) {
var key = row["key"];
var info = row["info"];
if (info) {
info = JSON.parse(info);
}
newSessions[key] = {
target: { host: row["host"], port: parseInt(row["port"]) },
key_type: row["key_type"],
token: row["token"],
requires_path_in_url: info?.requires_path_in_url,
requires_path_in_header_named: info?.requires_path_in_header_named,
};
},
finish,
);
};
var finish = function () {
for (var oldSession in map) {
if (!(oldSession in newSessions)) {
delete map[oldSession];
}
}
for (var newSession in newSessions) {
map[newSession] = newSessions[newSession];
}
db.close();
};
var db = new sqlite3.Database(path, loadSessions);
};
var updateFromPostgres = function (path, map) {
if (!postgresClient) {
console.error(
"Error: pg-native is not installed. Cannot update from PostgreSQL database.",
);
process.exit(1);
}
var db = new postgresClient();
var loadedSessions = {};
db.connectSync(path);
var queryResult = db.querySync(
"SELECT key, key_type, token, host, port, info FROM gxitproxy",
);
for (var row of queryResult) {
let info = row.info;
if (info) {
info = JSON.parse(info);
}
loadedSessions[row.key] = {
target: { host: row.host, port: parseInt(row.port) },
key_type: row.key_type,
token: row.token,
requires_path_in_url: info?.requires_path_in_url,
requires_path_in_header_named: info?.requires_path_in_header_named,
};
}
for (var oldSession in map) {
if (!(oldSession in loadedSessions)) {
delete map[oldSession];
}
}
for (var loadedSession in loadedSessions) {
map[loadedSession] = loadedSessions[loadedSession];
}
// console.log("Updated map:", map)
db.end();
};
var watchPostgres = function (path, loadMap, pollingInterval) {
if (!postgresClient) {
console.error(
"Error: pg-native is not installed. Cannot watch PostgreSQL database.",
);
process.exit(1);
}
// poll the database every `pollingInterval` seconds
if (pollingInterval > 0) {
setInterval(loadMap, pollingInterval);
}
// watch changes using PostgresSQL asynchronous notifications
// (https://www.postgresql.org/docs/16/libpq-notify.html)
var db = new postgresClient();
db.connect(path, function (err) {
if (err) {
throw err;
}
db.on("notification", function (_msg) {
loadMap(path, loadMap);
});
db.query("LISTEN gxitproxy", function (err, _res) {
if (err) {
throw err;
}
});
});
// requires creating a notification function and a trigger for the gxitproxy
// table on the database (see README.md for more details)
// delivery of notifications is not guaranteed, therefore, combining polling
// with asynchronous notifications is strongly recommended
};
var mapFor = function (path, pollingInterval) {
var map = {};
var loadMap;
var watch;
if (path.endsWith(".sqlite")) {
loadMap = function () {
updateFromSqlite(path, map);
};
watch = watchFile;
} else if (path.startsWith("postgresql://")) {
loadMap = function () {
updateFromPostgres(path, map);
};
watch = function (path, loadMap) {
return watchPostgres(path, loadMap, pollingInterval);
};
} else {
loadMap = function () {
updateFromJson(path, map);
};
watch = watchFile;
}
console.log("Watching path " + path);
loadMap();
watch(path, loadMap);
return map;
};
exports.mapFor = mapFor;