chore: Allow websockets and collaboration service to run in the same process (#2674)
This commit is contained in:
parent
3610a7f4a2
commit
d443abfc57
@ -29,8 +29,8 @@ REDIS_URL=redis://localhost:6479
|
|||||||
URL=http://localhost:3000
|
URL=http://localhost:3000
|
||||||
PORT=3000
|
PORT=3000
|
||||||
|
|
||||||
# ALPHA – See [documentation](docs/SERVICES.md) on running the alpha version of
|
# See [documentation](docs/SERVICES.md) on running a separate collaboration
|
||||||
# the collaboration server.
|
# server, for normal operation this does not need to be set.
|
||||||
COLLABORATION_URL=
|
COLLABORATION_URL=
|
||||||
|
|
||||||
# To support uploading of images for avatars and document attachments an
|
# To support uploading of images for avatars and document attachments an
|
||||||
|
@ -96,9 +96,7 @@ function SettingsSidebar() {
|
|||||||
label={t("Security")}
|
label={t("Security")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{can.update &&
|
{can.update && env.DEPLOYMENT !== "hosted" && (
|
||||||
env.COLLABORATION_URL &&
|
|
||||||
env.DEPLOYMENT !== "hosted" && (
|
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
to="/settings/features"
|
to="/settings/features"
|
||||||
icon={<BeakerIcon color="currentColor" />}
|
icon={<BeakerIcon color="currentColor" />}
|
||||||
|
@ -9,7 +9,6 @@ import Checkbox from "components/Checkbox";
|
|||||||
import Heading from "components/Heading";
|
import Heading from "components/Heading";
|
||||||
import HelpText from "components/HelpText";
|
import HelpText from "components/HelpText";
|
||||||
import Scene from "components/Scene";
|
import Scene from "components/Scene";
|
||||||
import env from "env";
|
|
||||||
import useCurrentTeam from "hooks/useCurrentTeam";
|
import useCurrentTeam from "hooks/useCurrentTeam";
|
||||||
import useStores from "hooks/useStores";
|
import useStores from "hooks/useStores";
|
||||||
import useToasts from "hooks/useToasts";
|
import useToasts from "hooks/useToasts";
|
||||||
@ -53,8 +52,6 @@ function Features() {
|
|||||||
all team members.
|
all team members.
|
||||||
</Trans>
|
</Trans>
|
||||||
</HelpText>
|
</HelpText>
|
||||||
|
|
||||||
{env.COLLABORATION_URL && (
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={t("Collaborative editing")}
|
label={t("Collaborative editing")}
|
||||||
name="collaborativeEditing"
|
name="collaborativeEditing"
|
||||||
@ -62,7 +59,6 @@ function Features() {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
note="When enabled multiple people can edit documents at the same time (Beta)"
|
note="When enabled multiple people can edit documents at the same time (Beta)"
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</Scene>
|
</Scene>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -32,14 +32,13 @@ At least one worker process is required to process the [queues](../server/queues
|
|||||||
|
|
||||||
## Collaboration
|
## Collaboration
|
||||||
|
|
||||||
The service is in alpha and as such is not started by default. It must run
|
The service is in alpha and as such is not started by default. Start the service with:
|
||||||
separately to the `websockets` service, and will not start in the same process.
|
|
||||||
The `COLLABORATION_URL` must be set to the publicly accessible URL when running
|
|
||||||
the service. For example, if the app is hosted at `https://docs.example.com` you
|
|
||||||
may use something like: `COLLABORATION_URL=wss://docs-collaboration.example.com`.
|
|
||||||
|
|
||||||
Start the service with:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn start --services=collaboration
|
yarn start --services=collaboration
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The collaboration service is ideally run on a separate server. If this is the
|
||||||
|
case the `COLLABORATION_URL` env can be set to the publicly accessible URL. For
|
||||||
|
example, if the app is hosted at `https://docs.example.com` you may use something
|
||||||
|
like: `COLLABORATION_URL=wss://docs-collaboration.example.com`.
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"build:webpack": "webpack --config webpack.config.prod.js",
|
"build:webpack": "webpack --config webpack.config.prod.js",
|
||||||
"build": "yarn clean && yarn build:webpack && yarn build:i18n && yarn build:server",
|
"build": "yarn clean && yarn build:webpack && yarn build:i18n && yarn build:server",
|
||||||
"start": "node ./build/server/index.js",
|
"start": "node ./build/server/index.js",
|
||||||
"dev": "yarn concurrently -n api,collaboration -c \"blue,magenta\" \"node --inspect=0.0.0.0 build/server/index.js --services=websockets,admin,web,worker\" \"node build/server/index.js --services=collaboration --port=4000\"",
|
"dev": "yarn concurrently -n api,collaboration -c \"blue,magenta\" \"node --inspect=0.0.0.0 build/server/index.js --services=collaboration,websockets,admin,web,worker\"",
|
||||||
"dev:watch": "nodemon --exec \"yarn build:server && yarn build:i18n && yarn dev\" -e js --ignore build/ --ignore app/ --ignore flow-typed/",
|
"dev:watch": "nodemon --exec \"yarn build:server && yarn build:i18n && yarn dev\" -e js --ignore build/ --ignore app/ --ignore flow-typed/",
|
||||||
"lint": "eslint app server shared",
|
"lint": "eslint app server shared",
|
||||||
"deploy": "git push heroku master",
|
"deploy": "git push heroku master",
|
||||||
@ -101,7 +101,6 @@
|
|||||||
"koa-body": "^4.2.0",
|
"koa-body": "^4.2.0",
|
||||||
"koa-compress": "2.0.0",
|
"koa-compress": "2.0.0",
|
||||||
"koa-convert": "1.2.0",
|
"koa-convert": "1.2.0",
|
||||||
"koa-easy-ws": "^1.3.0",
|
|
||||||
"koa-helmet": "5.2.0",
|
"koa-helmet": "5.2.0",
|
||||||
"koa-jwt": "^3.6.0",
|
"koa-jwt": "^3.6.0",
|
||||||
"koa-logger": "^3.2.1",
|
"koa-logger": "^3.2.1",
|
||||||
@ -175,6 +174,7 @@
|
|||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"validator": "5.2.0",
|
"validator": "5.2.0",
|
||||||
"winston": "^3.3.3",
|
"winston": "^3.3.3",
|
||||||
|
"ws": "^7.5.3",
|
||||||
"y-indexeddb": "^9.0.6",
|
"y-indexeddb": "^9.0.6",
|
||||||
"y-prosemirror": "^1.0.9",
|
"y-prosemirror": "^1.0.9",
|
||||||
"yjs": "^13.5.12"
|
"yjs": "^13.5.12"
|
||||||
|
@ -33,9 +33,17 @@ const serviceNames = uniq(
|
|||||||
|
|
||||||
// The number of processes to run, defaults to the number of CPU's available
|
// The number of processes to run, defaults to the number of CPU's available
|
||||||
// for the web service, and 1 for collaboration during the beta period.
|
// for the web service, and 1 for collaboration during the beta period.
|
||||||
const processCount = serviceNames.includes("collaboration")
|
let processCount = env.WEB_CONCURRENCY || undefined;
|
||||||
? 1
|
|
||||||
: env.WEB_CONCURRENCY || undefined;
|
if (serviceNames.includes("collaboration")) {
|
||||||
|
if (env.WEB_CONCURRENCY !== 1) {
|
||||||
|
Logger.info(
|
||||||
|
"lifecycle",
|
||||||
|
"Note: Restricting process count to 1 due to use of collaborative service"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
processCount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
// This function will only be called once in the original process
|
// This function will only be called once in the original process
|
||||||
function master() {
|
function master() {
|
||||||
@ -72,15 +80,6 @@ async function start(id: string, disconnect: () => void) {
|
|||||||
router.get("/_health", (ctx) => (ctx.body = "OK"));
|
router.get("/_health", (ctx) => (ctx.body = "OK"));
|
||||||
app.use(router.routes());
|
app.use(router.routes());
|
||||||
|
|
||||||
if (
|
|
||||||
serviceNames.includes("websockets") &&
|
|
||||||
serviceNames.includes("collaboration")
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
"Cannot run websockets and collaboration services in the same process"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop through requested services at startup
|
// loop through requested services at startup
|
||||||
for (const name of serviceNames) {
|
for (const name of serviceNames) {
|
||||||
if (!Object.keys(services).includes(name)) {
|
if (!Object.keys(services).includes(name)) {
|
||||||
|
@ -6,7 +6,7 @@ export default function present(env: Object): Object {
|
|||||||
return {
|
return {
|
||||||
URL: env.URL.replace(/\/$/, ""),
|
URL: env.URL.replace(/\/$/, ""),
|
||||||
CDN_URL: (env.CDN_URL || "").replace(/\/$/, ""),
|
CDN_URL: (env.CDN_URL || "").replace(/\/$/, ""),
|
||||||
COLLABORATION_URL: (env.COLLABORATION_URL || "")
|
COLLABORATION_URL: (env.COLLABORATION_URL || env.URL)
|
||||||
.replace(/\/$/, "")
|
.replace(/\/$/, "")
|
||||||
.replace(/^http/, "ws"),
|
.replace(/^http/, "ws"),
|
||||||
DEPLOYMENT: env.DEPLOYMENT,
|
DEPLOYMENT: env.DEPLOYMENT,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import env from "../env";
|
|
||||||
import { Team } from "../models";
|
import { Team } from "../models";
|
||||||
|
|
||||||
export default function present(team: Team) {
|
export default function present(team: Team) {
|
||||||
@ -8,9 +7,7 @@ export default function present(team: Team) {
|
|||||||
name: team.name,
|
name: team.name,
|
||||||
avatarUrl: team.logoUrl,
|
avatarUrl: team.logoUrl,
|
||||||
sharing: team.sharing,
|
sharing: team.sharing,
|
||||||
collaborativeEditing: !!(
|
collaborativeEditing: team.collaborativeEditing,
|
||||||
team.collaborativeEditing && env.COLLABORATION_URL
|
|
||||||
),
|
|
||||||
documentEmbeds: team.documentEmbeds,
|
documentEmbeds: team.documentEmbeds,
|
||||||
guestSignin: team.guestSignin,
|
guestSignin: team.guestSignin,
|
||||||
subdomain: team.subdomain,
|
subdomain: team.subdomain,
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import http from "http";
|
import http from "http";
|
||||||
|
import url from "url";
|
||||||
import { Server } from "@hocuspocus/server";
|
import { Server } from "@hocuspocus/server";
|
||||||
import Koa from "koa";
|
import Koa from "koa";
|
||||||
import websocket from "koa-easy-ws";
|
import WebSocket from "ws";
|
||||||
import Router from "koa-router";
|
|
||||||
import AuthenticationExtension from "../collaboration/authentication";
|
import AuthenticationExtension from "../collaboration/authentication";
|
||||||
import LoggerExtension from "../collaboration/logger";
|
import LoggerExtension from "../collaboration/logger";
|
||||||
import PersistenceExtension from "../collaboration/persistence";
|
import PersistenceExtension from "../collaboration/persistence";
|
||||||
import TracingExtension from "../collaboration/tracing";
|
import TracingExtension from "../collaboration/tracing";
|
||||||
|
|
||||||
export default function init(app: Koa, server: http.Server) {
|
export default function init(app: Koa, server: http.Server) {
|
||||||
const router = new Router();
|
const path = "/collaboration";
|
||||||
|
const wss = new WebSocket.Server({ noServer: true });
|
||||||
|
|
||||||
const hocuspocus = Server.configure({
|
const hocuspocus = Server.configure({
|
||||||
extensions: [
|
extensions: [
|
||||||
@ -21,21 +22,15 @@ export default function init(app: Koa, server: http.Server) {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Websockets for collaborative editing
|
server.on("upgrade", function (req, socket, head) {
|
||||||
router.get("/collaboration/:documentName", async (ctx) => {
|
if (req.url.indexOf(path) > -1) {
|
||||||
let { documentName } = ctx.params;
|
const documentName = url.parse(req.url).pathname?.split("/").pop();
|
||||||
|
|
||||||
if (ctx.ws) {
|
wss.handleUpgrade(req, socket, Buffer.alloc(0), (client) => {
|
||||||
const ws = await ctx.ws();
|
hocuspocus.handleConnection(client, req, documentName);
|
||||||
hocuspocus.handleConnection(ws, ctx.request, documentName);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.response.status = 101;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(websocket());
|
|
||||||
app.use(router.routes());
|
|
||||||
app.use(router.allowedMethods());
|
|
||||||
|
|
||||||
server.on("shutdown", () => {
|
server.on("shutdown", () => {
|
||||||
hocuspocus.destroy();
|
hocuspocus.destroy();
|
||||||
|
@ -16,13 +16,28 @@ import { getUserForJWT } from "../utils/jwt";
|
|||||||
const { can } = policy;
|
const { can } = policy;
|
||||||
|
|
||||||
export default function init(app: Koa, server: http.Server) {
|
export default function init(app: Koa, server: http.Server) {
|
||||||
|
const path = "/realtime";
|
||||||
|
|
||||||
// Websockets for events and non-collaborative documents
|
// Websockets for events and non-collaborative documents
|
||||||
const io = IO(server, {
|
const io = IO(server, {
|
||||||
path: "/realtime",
|
path,
|
||||||
serveClient: false,
|
serveClient: false,
|
||||||
cookie: false,
|
cookie: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Remove the upgrade handler that we just added when registering the IO engine
|
||||||
|
// And re-add it with a check to only handle the realtime path, this allows
|
||||||
|
// collaboration websockets to exist in the same process as engine.io.
|
||||||
|
const listeners = server.listeners("upgrade");
|
||||||
|
const ioHandleUpgrade = listeners.pop();
|
||||||
|
server.removeListener("upgrade", ioHandleUpgrade);
|
||||||
|
|
||||||
|
server.on("upgrade", function (req, socket, head) {
|
||||||
|
if (req.url.indexOf(path) > -1) {
|
||||||
|
ioHandleUpgrade(req, socket, head);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.on("shutdown", () => {
|
server.on("shutdown", () => {
|
||||||
Metrics.gaugePerInstance("websockets.count", 0);
|
Metrics.gaugePerInstance("websockets.count", 0);
|
||||||
});
|
});
|
||||||
|
16
yarn.lock
16
yarn.lock
@ -9261,14 +9261,6 @@ koa-convert@1.2.0, koa-convert@^1.2.0:
|
|||||||
co "^4.6.0"
|
co "^4.6.0"
|
||||||
koa-compose "^3.0.0"
|
koa-compose "^3.0.0"
|
||||||
|
|
||||||
koa-easy-ws@^1.3.0:
|
|
||||||
version "1.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/koa-easy-ws/-/koa-easy-ws-1.3.0.tgz#dbf8eeb126ca11eed4c4e3af7904a47c25be2347"
|
|
||||||
integrity sha512-06lHAwm25HBdplTpwHiZqKcl39vS4KiVzRPneUExCHVvQ9TPHmFlUFO/gjQrUdLj3+kvqQpqdlIPNb91wn6EwA==
|
|
||||||
dependencies:
|
|
||||||
debug "^4.1.1"
|
|
||||||
ws "^7.3.1"
|
|
||||||
|
|
||||||
koa-helmet@5.2.0:
|
koa-helmet@5.2.0:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/koa-helmet/-/koa-helmet-5.2.0.tgz#6529f64dd4539261a9bb0a56e201e4976f0200f0"
|
resolved "https://registry.yarnpkg.com/koa-helmet/-/koa-helmet-5.2.0.tgz#6529f64dd4539261a9bb0a56e201e4976f0200f0"
|
||||||
@ -15496,10 +15488,10 @@ write@1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mkdirp "^0.5.1"
|
mkdirp "^0.5.1"
|
||||||
|
|
||||||
ws@^7.2.3, ws@^7.3.1, ws@^7.4.3:
|
ws@^7.2.3, ws@^7.4.3, ws@^7.5.3:
|
||||||
version "7.5.3"
|
version "7.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881"
|
||||||
integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==
|
integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==
|
||||||
|
|
||||||
ws@~7.4.2:
|
ws@~7.4.2:
|
||||||
version "7.4.6"
|
version "7.4.6"
|
||||||
|
Reference in New Issue
Block a user