CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/project/servers/browser/http-server.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
/*
7
This is an express http server that is meant to receive connections
8
only from web browser clients that signed in as collaborators on
9
this projects. It serves both HTTP and websocket connections, which
10
should be proxied through some hub.
11
*/
12
13
import bodyParser from "body-parser";
14
import compression from "compression";
15
import express from "express";
16
import { createServer } from "http";
17
import { writeFile } from "node:fs/promises";
18
import { join } from "node:path";
19
20
import basePath from "@cocalc/backend/base-path";
21
import initWebsocket from "@cocalc/project/browser-websocket/server";
22
import initWebsocketFs from "../websocketfs";
23
import initSyncFs from "../sync-fs";
24
import { browserPortFile, project_id } from "@cocalc/project/data";
25
import initDirectoryListing from "@cocalc/project/directory-listing";
26
import { getOptions } from "@cocalc/project/init-program";
27
import initJupyter from "@cocalc/project/jupyter/http-server";
28
import * as kucalc from "@cocalc/project/kucalc";
29
import { getLogger } from "@cocalc/project/logger";
30
import initUpload from "@cocalc/project/upload";
31
import { once } from "@cocalc/util/async-utils";
32
import initRootSymbolicLink from "./root-symlink";
33
import initStaticServer from "./static";
34
35
const winston = getLogger("browser-http-server");
36
37
export default async function init(): Promise<void> {
38
winston.info("starting server...");
39
40
const base = join(basePath, project_id, "raw") + "/";
41
42
const app = express();
43
app.disable("x-powered-by"); // https://github.com/sagemathinc/cocalc/issues/6101
44
45
const server = createServer(app);
46
47
// WEBSOCKET SERVERS
48
// **CRITICAL:** This *must* be above the app.use(compression())
49
// middleware below, since compressing/uncompressing the websocket
50
// would otherwise happen, and that slows it down a lot.
51
// Setup the ws websocket server, which is used by clients
52
// for direct websocket connections to the project, and also
53
// serves primus.js, which is the relevant client library.
54
winston.info("initializing websocket server");
55
// We have to explicitly also include the base as a parameter
56
// to initWebsocket, since of course it makes deeper user of server.
57
app.use(base, initWebsocket(server, base));
58
initWebsocketFs(server, base);
59
// This uses its own internal lz4 compression:
60
initSyncFs(server, base);
61
62
// CRITICAL: keep this after the websocket stuff or anything you do not
63
// want to have compressed.
64
// suggested by http://expressjs.com/en/advanced/best-practice-performance.html#use-gzip-compression
65
app.use(compression());
66
67
// Needed for POST file to custom path, which is used for uploading files to projects.
68
// parse application/x-www-form-urlencoded
69
app.use(bodyParser.urlencoded({ extended: true }));
70
// parse application/json
71
app.use(bodyParser.json());
72
73
winston.info("creating root symbolic link");
74
await initRootSymbolicLink();
75
76
if (kucalc.IN_KUCALC) {
77
// Add /health (used as a health check for Kubernetes) and /metrics (Prometheus)
78
winston.info("initializing KuCalc only health metrics server");
79
kucalc.init_health_metrics(app, project_id);
80
}
81
82
// Setup the directory_listing/... server, which is used to provide directory listings
83
// to the hub (at least in KuCalc). It is still used by HUB! But why? Maybe it is only
84
// for the deprecated public access to a project? If so, we can get rid of all of that.
85
winston.info("initializing directory listings server (DEPRECATED)");
86
app.use(base, initDirectoryListing());
87
88
// Setup the jupyter/... server, which is used by our jupyter server for blobs, etc.
89
winston.info("initializing Jupyter support HTTP server");
90
(async () => {
91
// if the BlobStore isn't available immediately, this will take a while to initialize, and
92
// we don't want to block the remainder of this setup...
93
app.use(base, await initJupyter());
94
})();
95
96
// Setup the upload POST endpoint
97
winston.info("initializing file upload server");
98
app.use(base, initUpload());
99
100
winston.info("initializing static server");
101
initStaticServer(app, base);
102
103
const options = getOptions();
104
server.listen(options.browserPort, options.hostname);
105
await once(server, "listening");
106
const address = server.address();
107
if (address == null || typeof address == "string") {
108
// null = failed; string doesn't happen since that's for unix domain
109
// sockets, which we aren't using.
110
// This is probably impossible, but it makes typescript happier.
111
throw Error("failed to assign a port");
112
}
113
const assignedPort = address.port; // may be a server assigned random port.
114
winston.info(
115
`Started -- port=${assignedPort}, host='${options.hostname}', base='${base}'`,
116
);
117
118
winston.info(`Writing port to ${browserPortFile}`);
119
await writeFile(browserPortFile, `${assignedPort}`);
120
}
121
122