mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) make Grist easier to run with a single server
Summary: This makes many small changes so that Grist is less fussy to run as a single instance behind a reverse proxy. Some users had difficulty with the self-connections Grist would make, due to internal network setup, and since these are unnecessary in any case in this scenario, they are now optimized away. Likewise some users had difficulties related to doc worker urls, which are now also optimized away. With these changes, users should be able to get a lot further on first try, at least far enough to open and edit documents. The `GRIST_SINGLE_ORG` setting was proving a bit confusing, since it appeared to only work when set to `docs`. This diff adds a check for whether the specified org exists, and if not, it creates it. This still depends on having a user email to make as the owner of the team, so there could be remaining difficulties there. Test Plan: tested manually with nginx Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3299
This commit is contained in:
@@ -17,6 +17,7 @@ import {assertAccess, getTransitiveHeaders, getUserId, isAnonymousUser,
|
||||
RequestWithLogin} from 'app/server/lib/Authorizer';
|
||||
import {DocStatus, IDocWorkerMap} from 'app/server/lib/DocWorkerMap';
|
||||
import {expressWrap} from 'app/server/lib/expressWrap';
|
||||
import {DocTemplate, GristServer} from 'app/server/lib/GristServer';
|
||||
import {getAssignmentId} from 'app/server/lib/idUtils';
|
||||
import * as log from 'app/server/lib/log';
|
||||
import {adaptServerUrl, addOrgToPathIfNeeded, pruneAPIResult, trustOrigin} from 'app/server/lib/requestUtils';
|
||||
@@ -31,6 +32,7 @@ export interface AttachOptions {
|
||||
sendAppPage: (req: express.Request, resp: express.Response, options: ISendAppPageOptions) => Promise<void>;
|
||||
dbManager: HomeDBManager;
|
||||
plugins: LocalPlugin[];
|
||||
gristServer: GristServer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +63,13 @@ export interface AttachOptions {
|
||||
* TODO: doc worker registration could be redesigned to remove the assumption
|
||||
* of a fixed base domain.
|
||||
*/
|
||||
function customizeDocWorkerUrl(docWorkerUrlSeed: string, req: express.Request) {
|
||||
function customizeDocWorkerUrl(docWorkerUrlSeed: string|undefined, req: express.Request): string|null {
|
||||
if (!docWorkerUrlSeed) {
|
||||
// When no doc worker seed, we're in single server mode.
|
||||
// Return null, to signify that the URL prefix serving the
|
||||
// current endpoint is the only one available.
|
||||
return null;
|
||||
}
|
||||
const docWorkerUrl = new URL(docWorkerUrlSeed);
|
||||
const workerSubdomain = parseSubdomainStrictly(docWorkerUrl.hostname).org;
|
||||
adaptServerUrl(docWorkerUrl, req);
|
||||
@@ -105,6 +113,14 @@ function customizeDocWorkerUrl(docWorkerUrlSeed: string, req: express.Request) {
|
||||
*/
|
||||
async function getWorker(docWorkerMap: IDocWorkerMap, assignmentId: string,
|
||||
urlPath: string, config: RequestInit = {}) {
|
||||
if (!useWorkerPool()) {
|
||||
// This should never happen. We are careful to not use getWorker
|
||||
// when everything is on a single server, since it is burdensome
|
||||
// for self-hosted users to figure out the correct settings for
|
||||
// the server to be able to contact itself, and there are cases
|
||||
// of the defaults not working.
|
||||
throw new Error("AppEndpoint.getWorker was called unnecessarily");
|
||||
}
|
||||
let docStatus: DocStatus|undefined;
|
||||
const workersAreManaged = Boolean(process.env.GRIST_MANAGED_WORKERS);
|
||||
for (;;) {
|
||||
@@ -151,13 +167,27 @@ async function getWorker(docWorkerMap: IDocWorkerMap, assignmentId: string,
|
||||
}
|
||||
|
||||
export function attachAppEndpoint(options: AttachOptions): void {
|
||||
const {app, middleware, docMiddleware, docWorkerMap, forceLogin, sendAppPage, dbManager, plugins} = options;
|
||||
const {app, middleware, docMiddleware, docWorkerMap, forceLogin,
|
||||
sendAppPage, dbManager, plugins, gristServer} = options;
|
||||
// Per-workspace URLs open the same old Home page, and it's up to the client to notice and
|
||||
// render the right workspace.
|
||||
app.get(['/', '/ws/:wsId', '/p/:page'], ...middleware, expressWrap(async (req, res) =>
|
||||
sendAppPage(req, res, {path: 'app.html', status: 200, config: {plugins}, googleTagManager: 'anon'})));
|
||||
|
||||
app.get('/api/worker/:assignmentId([^/]+)/?*', expressWrap(async (req, res) => {
|
||||
if (!useWorkerPool()) {
|
||||
// Let the client know there is not a separate pool of workers,
|
||||
// so they should continue to use the same base URL for accessing
|
||||
// documents. For consistency, return a prefix to add into that
|
||||
// URL, as there would be for a pool of workers. It would be nice
|
||||
// to go ahead and provide the full URL, but that requires making
|
||||
// more assumptions about how Grist is configured.
|
||||
// Alternatives could be: have the client to send their base URL
|
||||
// in the request; or use headers commonly added by reverse proxies.
|
||||
const selfPrefix = "/dw/self/v/" + gristServer.getTag();
|
||||
res.json({docWorkerUrl: null, selfPrefix});
|
||||
return;
|
||||
}
|
||||
if (!trustOrigin(req, res)) { throw new Error('Unrecognized origin'); }
|
||||
res.header("Access-Control-Allow-Credentials", "true");
|
||||
|
||||
@@ -237,25 +267,31 @@ export function attachAppEndpoint(options: AttachOptions): void {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// The reason to pass through app.html fetched from docWorker is in case it is a different
|
||||
// version of Grist (could be newer or older).
|
||||
// TODO: More must be done for correct version tagging of URLs: <base href> assumes all
|
||||
// links and static resources come from the same host, but we'll have Home API, DocWorker,
|
||||
// and static resources all at hostnames different from where this page is served.
|
||||
// TODO docWorkerMain needs to serve app.html, perhaps with correct base-href already set.
|
||||
let body: DocTemplate;
|
||||
let docStatus: DocStatus|undefined;
|
||||
const docId = doc.id;
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
...getTransitiveHeaders(req),
|
||||
};
|
||||
const {docStatus, resp} = await getWorker(docWorkerMap, docId,
|
||||
`/${docId}/app.html`, {headers});
|
||||
const body = await resp.json();
|
||||
if (!useWorkerPool()) {
|
||||
body = await gristServer.getDocTemplate();
|
||||
} else {
|
||||
// The reason to pass through app.html fetched from docWorker is in case it is a different
|
||||
// version of Grist (could be newer or older).
|
||||
// TODO: More must be done for correct version tagging of URLs: <base href> assumes all
|
||||
// links and static resources come from the same host, but we'll have Home API, DocWorker,
|
||||
// and static resources all at hostnames different from where this page is served.
|
||||
// TODO docWorkerMain needs to serve app.html, perhaps with correct base-href already set.
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
...getTransitiveHeaders(req),
|
||||
};
|
||||
const workerInfo = await getWorker(docWorkerMap, docId, `/${docId}/app.html`, {headers});
|
||||
docStatus = workerInfo.docStatus;
|
||||
body = await workerInfo.resp.json();
|
||||
}
|
||||
|
||||
await sendAppPage(req, res, {path: "", content: body.page, tag: body.tag, status: 200,
|
||||
googleTagManager: 'anon', config: {
|
||||
assignmentId: docId,
|
||||
getWorker: {[docId]: customizeDocWorkerUrl(docStatus.docWorker.publicUrl, req)},
|
||||
getWorker: {[docId]: customizeDocWorkerUrl(docStatus?.docWorker?.publicUrl, req)},
|
||||
getDoc: {[docId]: pruneAPIResult(doc as unknown as APIDocument)},
|
||||
plugins
|
||||
}});
|
||||
@@ -266,3 +302,8 @@ export function attachAppEndpoint(options: AttachOptions): void {
|
||||
app.get('/:urlId([^/]{12,})/:slug([^/]+):remainder(*)',
|
||||
...docMiddleware, docHandler);
|
||||
}
|
||||
|
||||
// Return true if document related endpoints are served by separate workers.
|
||||
function useWorkerPool() {
|
||||
return process.env.GRIST_SINGLE_PORT !== 'true';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user