mirror of https://github.com/coder/code-server.git
commit
02f9af1731
|
@ -13,8 +13,8 @@ router.get("/", (req, res) => {
|
||||||
export const wsRouter = WsRouter()
|
export const wsRouter = WsRouter()
|
||||||
|
|
||||||
wsRouter.ws("/", async (req) => {
|
wsRouter.ws("/", async (req) => {
|
||||||
wss.handleUpgrade(req, req.socket, req.head, (ws) => {
|
wss.handleUpgrade(req, req.ws, req.head, (ws) => {
|
||||||
ws.on("message", () => {
|
ws.addEventListener("message", () => {
|
||||||
ws.send(
|
ws.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
event: "health",
|
event: "health",
|
||||||
|
@ -23,5 +23,6 @@ wsRouter.ws("/", async (req) => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
req.ws.resume()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import * as httpserver from "./httpserver"
|
||||||
|
import * as integration from "./integration"
|
||||||
|
|
||||||
|
describe("health", () => {
|
||||||
|
let codeServer: httpserver.HttpServer | undefined
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
if (codeServer) {
|
||||||
|
await codeServer.close()
|
||||||
|
codeServer = undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("/healthz", async () => {
|
||||||
|
;[, , codeServer] = await integration.setup(["--auth=none"], "")
|
||||||
|
const resp = await codeServer.fetch("/healthz")
|
||||||
|
expect(resp.status).toBe(200)
|
||||||
|
const json = await resp.json()
|
||||||
|
expect(json).toStrictEqual({ lastHeartbeat: 0, status: "expired" })
|
||||||
|
})
|
||||||
|
|
||||||
|
it("/healthz (websocket)", async () => {
|
||||||
|
;[, , codeServer] = await integration.setup(["--auth=none"], "")
|
||||||
|
const ws = codeServer.ws("/healthz")
|
||||||
|
const message = await new Promise((resolve, reject) => {
|
||||||
|
ws.on("error", console.error)
|
||||||
|
ws.on("message", (message) => {
|
||||||
|
try {
|
||||||
|
const j = JSON.parse(message.toString())
|
||||||
|
resolve(j)
|
||||||
|
} catch (error) {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ws.on("open", () => ws.send(JSON.stringify({ event: "health" })))
|
||||||
|
})
|
||||||
|
ws.terminate()
|
||||||
|
expect(message).toStrictEqual({ event: "health", status: "expired", lastHeartbeat: 0 })
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,5 +1,6 @@
|
||||||
import * as express from "express"
|
import * as express from "express"
|
||||||
import * as http from "http"
|
import * as http from "http"
|
||||||
|
import * as net from "net"
|
||||||
import * as nodeFetch from "node-fetch"
|
import * as nodeFetch from "node-fetch"
|
||||||
import Websocket from "ws"
|
import Websocket from "ws"
|
||||||
import * as util from "../src/common/util"
|
import * as util from "../src/common/util"
|
||||||
|
@ -8,13 +9,21 @@ import { handleUpgrade } from "../src/node/wsRouter"
|
||||||
|
|
||||||
// Perhaps an abstraction similar to this should be used in app.ts as well.
|
// Perhaps an abstraction similar to this should be used in app.ts as well.
|
||||||
export class HttpServer {
|
export class HttpServer {
|
||||||
private hs = http.createServer()
|
private readonly sockets = new Set<net.Socket>()
|
||||||
|
private cleanupTimeout?: NodeJS.Timeout
|
||||||
|
|
||||||
public constructor(hs?: http.Server) {
|
|
||||||
// See usage in test/integration.ts
|
// See usage in test/integration.ts
|
||||||
if (hs) {
|
public constructor(private readonly hs = http.createServer()) {
|
||||||
this.hs = hs
|
this.hs.on("connection", (socket) => {
|
||||||
|
this.sockets.add(socket)
|
||||||
|
socket.on("close", () => {
|
||||||
|
this.sockets.delete(socket)
|
||||||
|
if (this.cleanupTimeout && this.sockets.size === 0) {
|
||||||
|
clearTimeout(this.cleanupTimeout)
|
||||||
|
this.cleanupTimeout = undefined
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,6 +63,8 @@ export class HttpServer {
|
||||||
*/
|
*/
|
||||||
public close(): Promise<void> {
|
public close(): Promise<void> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
|
// Close will not actually close anything; it just waits until everything
|
||||||
|
// is closed.
|
||||||
this.hs.close((err) => {
|
this.hs.close((err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
rej(err)
|
rej(err)
|
||||||
|
@ -61,6 +72,19 @@ export class HttpServer {
|
||||||
}
|
}
|
||||||
res()
|
res()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// If there are sockets remaining we might need to force close them or
|
||||||
|
// this promise might never resolve.
|
||||||
|
if (this.sockets.size > 0) {
|
||||||
|
// Give sockets a chance to close up shop.
|
||||||
|
this.cleanupTimeout = setTimeout(() => {
|
||||||
|
this.cleanupTimeout = undefined
|
||||||
|
for (const socket of this.sockets.values()) {
|
||||||
|
console.warn("a socket was left hanging")
|
||||||
|
socket.destroy()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,8 @@ export const plugin: cs.Plugin = {
|
||||||
wsRouter() {
|
wsRouter() {
|
||||||
const wr = cs.WsRouter()
|
const wr = cs.WsRouter()
|
||||||
wr.ws("/test-app", (req) => {
|
wr.ws("/test-app", (req) => {
|
||||||
cs.wss.handleUpgrade(req, req.socket, req.head, (ws) => {
|
cs.wss.handleUpgrade(req, req.ws, req.head, (ws) => {
|
||||||
|
req.ws.resume()
|
||||||
ws.send("hello")
|
ws.send("hello")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue