Skip to content

Commit ad7da73

Browse files
committed
Refactor vscode router to load async.
1 parent d052cc2 commit ad7da73

File tree

2 files changed

+85
-53
lines changed

2 files changed

+85
-53
lines changed

src/node/routes/index.ts

+9-17
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { HttpCode, HttpError } from "../../common/http"
1010
import { plural } from "../../common/util"
1111
import { App } from "../app"
1212
import { AuthType, DefaultedArgs } from "../cli"
13-
import { commit, isDevMode, rootPath } from "../constants"
13+
import { commit, rootPath } from "../constants"
1414
import { Heart } from "../heart"
1515
import { ensureAuthenticated, redirect } from "../http"
1616
import { PluginAPI } from "../plugin"
@@ -23,7 +23,7 @@ import * as login from "./login"
2323
import * as logout from "./logout"
2424
import * as pathProxy from "./pathProxy"
2525
import * as update from "./update"
26-
import { createVSServerRouter, VSServerResult } from "./vscode"
26+
import { CodeServerRouteWrapper } from "./vscode"
2727

2828
/**
2929
* Register all routes and middleware.
@@ -138,20 +138,12 @@ export const register = async (app: App, args: DefaultedArgs): Promise<Disposabl
138138

139139
app.router.use("/update", update.router)
140140

141-
let vscode: VSServerResult
142-
try {
143-
vscode = await createVSServerRouter(args)
144-
app.router.use("/", vscode.router)
145-
app.wsRouter.use("/", vscode.wsRouter.router)
146-
app.router.use("/vscode", vscode.router)
147-
app.wsRouter.use("/vscode", vscode.wsRouter.router)
148-
} catch (error: any) {
149-
if (isDevMode) {
150-
logger.warn(error)
151-
logger.warn("VS Server router may still be compiling.")
152-
} else {
153-
throw error
154-
}
141+
const vsServerRouteHandler = new CodeServerRouteWrapper()
142+
143+
// Note that the root route is replaced in Coder Enterprise by the plugin API.
144+
for (const routePrefix of ["/", "/vscode"]) {
145+
app.router.use(routePrefix, vsServerRouteHandler.router)
146+
app.wsRouter.use(routePrefix, vsServerRouteHandler.wsRouter)
155147
}
156148

157149
app.router.use(() => {
@@ -164,6 +156,6 @@ export const register = async (app: App, args: DefaultedArgs): Promise<Disposabl
164156
return () => {
165157
heart.dispose()
166158
pluginApi?.dispose()
167-
vscode?.codeServerMain.dispose()
159+
vsServerRouteHandler.dispose()
168160
}
169161
}

src/node/routes/vscode.ts

+76-36
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,105 @@
1+
import { logger } from "@coder/logger"
12
import * as express from "express"
2-
import { DefaultedArgs } from "../cli"
3+
import { WebsocketRequest } from "../../../typings/pluginapi"
4+
import { logError } from "../../common/util"
5+
import { isDevMode } from "../constants"
36
import { ensureAuthenticated, authenticated, redirect } from "../http"
47
import { loadAMDModule } from "../util"
5-
import { Router as WsRouter, WebsocketRouter } from "../wsRouter"
8+
import { Router as WsRouter } from "../wsRouter"
69
import { errorHandler } from "./errors"
710

8-
export interface VSServerResult {
9-
router: express.Router
10-
wsRouter: WebsocketRouter
11-
codeServerMain: CodeServerLib.IServerAPI
12-
}
13-
14-
export const createVSServerRouter = async (args: DefaultedArgs): Promise<VSServerResult> => {
15-
// See ../../../vendor/modules/code-oss-dev/src/vs/server/main.js.
16-
const createVSServer = await loadAMDModule<CodeServerLib.CreateServer>(
17-
"vs/server/remoteExtensionHostAgent",
18-
"createServer",
19-
)
11+
export class CodeServerRouteWrapper {
12+
/** Assigned in `ensureCodeServerLoaded` */
13+
private _codeServerMain!: CodeServerLib.IServerAPI
14+
private _wsRouterWrapper = WsRouter()
15+
public router = express.Router()
2016

21-
const codeServerMain = await createVSServer(null, {
22-
connectionToken: "0000",
23-
...args,
24-
// For some reason VS Code takes the port as a string.
25-
port: typeof args.port !== "undefined" ? args.port.toString() : undefined,
26-
})
17+
public get wsRouter() {
18+
return this._wsRouterWrapper.router
19+
}
2720

28-
const router = express.Router()
29-
const wsRouter = WsRouter()
21+
//#region Route Handlers
3022

31-
router.get("/", async (req, res, next) => {
23+
private $root: express.Handler = async (req, res, next) => {
3224
const isAuthenticated = await authenticated(req)
25+
3326
if (!isAuthenticated) {
3427
return redirect(req, res, "login", {
3528
// req.baseUrl can be blank if already at the root.
3629
to: req.baseUrl && req.baseUrl !== "/" ? req.baseUrl : undefined,
3730
})
3831
}
32+
3933
next()
40-
})
34+
}
4135

42-
router.all("*", ensureAuthenticated, (req, res, next) => {
43-
req.on("error", (error: any) => {
36+
private $proxyRequest: express.Handler = async (req, res, next) => {
37+
// We allow certain errors to propagate so that other routers may handle requests
38+
// outside VS Code
39+
const requestErrorHandler = (error: any) => {
4440
if (error instanceof Error && ["EntryNotFound", "FileNotFound", "HttpError"].includes(error.message)) {
4541
next()
4642
}
4743

4844
errorHandler(error, req, res, next)
49-
})
45+
}
46+
47+
req.once("error", requestErrorHandler)
5048

51-
codeServerMain.handleRequest(req, res)
52-
})
49+
this._codeServerMain.handleRequest(req, res)
50+
}
5351

54-
wsRouter.ws("/", ensureAuthenticated, (req) => {
55-
codeServerMain.handleUpgrade(req, req.socket)
52+
private $proxyWebsocket = async (req: WebsocketRequest) => {
53+
this._codeServerMain.handleUpgrade(req, req.socket)
5654

5755
req.socket.resume()
58-
})
56+
}
57+
58+
//#endregion
59+
60+
/**
61+
* Fetches a code server instance asynchronously to avoid an initial memory overhead.
62+
*/
63+
private ensureCodeServerLoaded: express.Handler = async (req, _res, next) => {
64+
if (this._codeServerMain) {
65+
return next()
66+
}
67+
68+
const { args } = req
69+
70+
/**
71+
* @file ../../../vendor/modules/code-oss-dev/src/vs/server/main.js
72+
*/
73+
const createVSServer = await loadAMDModule<CodeServerLib.CreateServer>(
74+
"vs/server/remoteExtensionHostAgent",
75+
"createServer",
76+
)
77+
78+
try {
79+
this._codeServerMain = await createVSServer(null, {
80+
connectionToken: "0000",
81+
...args,
82+
// For some reason VS Code takes the port as a string.
83+
port: args.port?.toString(),
84+
})
85+
} catch (createServerError) {
86+
logError(logger, "CodeServerRouteWrapper", createServerError)
87+
88+
const loggedError = isDevMode ? new Error("VS Code may still be compiling...") : createServerError
89+
90+
return next(loggedError)
91+
}
92+
93+
return next()
94+
}
95+
96+
constructor() {
97+
this.router.get("/", this.ensureCodeServerLoaded, this.$root)
98+
this.router.all("*", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyRequest)
99+
this._wsRouterWrapper.ws("/", ensureAuthenticated, this.ensureCodeServerLoaded, this.$proxyWebsocket)
100+
}
59101

60-
return {
61-
router,
62-
wsRouter,
63-
codeServerMain,
102+
dispose() {
103+
this._codeServerMain?.dispose()
64104
}
65105
}

0 commit comments

Comments
 (0)