UNPKG

smc-hub

Version:

CoCalc: Backend webserver component

187 lines (186 loc) 10.3 kB
"use strict"; /* Given a URL that we need to proxy, determine the target (host and port) that is being proxied. Throws an error if anything goes wrong, e.g., user doesn't have access to this target or the target project isn't running. */ 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 __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getTarget = exports.invalidateTargetCache = void 0; var lru_cache_1 = __importDefault(require("lru-cache")); var async_utils_1 = require("smc-util/async-utils"); var parse_1 = require("./parse"); var logger_1 = __importDefault(require("../logger")); var check_for_access_to_project_1 = __importDefault(require("./check-for-access-to-project")); var hub_projects = require("../projects"); var database_1 = require("../servers/database"); var winston = logger_1.default("proxy: target"); // The cached entries expire after 30 seconds. Caching the target // helps enormously when there is a burst of requests. // Also if a project restarts, the browser port might change and we // don't want to have to fix this via getting an error. // Also, if the project stops and starts, the host=ip address could // change, so we need to timeout so we see that thange. var cache = new lru_cache_1.default({ max: 20000, maxAge: 1000 * 30 }); // This gets explicitly called from outside when certain errors occur. function invalidateTargetCache(remember_me, url) { var key = parse_1.parseReq(url, remember_me).key; winston.debug("invalidateCache: " + url); cache.del(key); } exports.invalidateTargetCache = invalidateTargetCache; function getTarget(opts) { return __awaiter(this, void 0, void 0, function () { var remember_me, url, isPersonal, projectControl, _a, key, type, project_id, port_desc, internal_url, dbg, project, state, host, port, status_1, target; return __generator(this, function (_b) { switch (_b.label) { case 0: remember_me = opts.remember_me, url = opts.url, isPersonal = opts.isPersonal, projectControl = opts.projectControl; _a = parse_1.parseReq(url, remember_me), key = _a.key, type = _a.type, project_id = _a.project_id, port_desc = _a.port_desc, internal_url = _a.internal_url; if (cache.has(key)) { return [2 /*return*/, cache.get(key)]; } dbg = function (m) { return winston.debug("target(" + key + "): " + m); }; dbg("url=" + url); if (!(remember_me != null)) return [3 /*break*/, 2]; return [4 /*yield*/, check_for_access_to_project_1.default({ project_id: project_id, remember_me: remember_me, type: "write", isPersonal: isPersonal })]; case 1: // For now, we always require write access to proxy. // We really haven't implemented a notion of "read access" to projects, // instead focusing on public sharing, cloning, etc. if (!(_b.sent())) { throw Error("user does not have write access to project"); } _b.label = 2; case 2: project = projectControl(project_id); return [4 /*yield*/, project.state()]; case 3: state = _b.sent(); host = state.ip; dbg("host=" + host); if (!(port_desc == "jupyter" || port_desc == "jupyterlab")) return [3 /*break*/, 7]; if (!(host == null || state.state != "running")) return [3 /*break*/, 6]; // We just start the project. // This is used specifically by Juno, but also makes it // easier to continually use Jupyter/Lab without having // to worry about the cocalc project. dbg("project not running and jupyter requested, so starting to run " + port_desc); return [4 /*yield*/, project.start()]; case 4: _b.sent(); return [4 /*yield*/, project.state()]; case 5: state = _b.sent(); host = state.ip; return [3 /*break*/, 7]; case 6: // Touch project so it doesn't idle timeout database_1.database.touch_project({ project_id: project_id }); _b.label = 7; case 7: if (host == null) { throw Error("host is undefined -- project not running"); } if (state.state != "running") { throw Error("project is not running"); } if (!(type === "port" || type === "server")) return [3 /*break*/, 13]; if (!(port_desc === "jupyter")) return [3 /*break*/, 9]; dbg("determining jupyter server port..."); return [4 /*yield*/, jupyterPort(project_id, projectControl, false)]; case 8: port = _b.sent(); dbg("got jupyter port=" + port); return [3 /*break*/, 12]; case 9: if (!(port_desc === "jupyterlab")) return [3 /*break*/, 11]; dbg("determining jupyter server port..."); return [4 /*yield*/, jupyterPort(project_id, projectControl, true)]; case 10: port = _b.sent(); dbg("got jupyterlab port=" + port); return [3 /*break*/, 12]; case 11: port = parseInt(port_desc); _b.label = 12; case 12: return [3 /*break*/, 16]; case 13: if (!(type === "raw")) return [3 /*break*/, 15]; return [4 /*yield*/, project.status()]; case 14: status_1 = _b.sent(); // connection to the HTTP server in the project that serves web browsers if (status_1["browser-server.port"]) { port = status_1["browser-server.port"]; } else { throw Error("project browser server port not available -- project might not be opened or running"); } return [3 /*break*/, 16]; case 15: throw Error("unknown url type -- " + type); case 16: dbg("finished: host=" + host + "; port=" + port + "; type=" + type); target = { host: host, port: port, internal_url: internal_url }; cache.set(key, target); return [2 /*return*/, target]; } }); }); } exports.getTarget = getTarget; function jupyterPort(project_id, projectControl, lab) { return __awaiter(this, void 0, void 0, function () { var project; return __generator(this, function (_a) { switch (_a.label) { case 0: project = hub_projects.new_project( // NOT project-control like above... project_id, database_1.database, projectControl); return [4 /*yield*/, async_utils_1.callback2(project.jupyter_port, { lab: lab })]; case 1: return [2 /*return*/, _a.sent()]; } }); }); } //# sourceMappingURL=target.js.map