Skip to content

Commit 75c8fde

Browse files
authored
Added /healthz JSON response for heartbeat data. #1940 (#1984)
1 parent de41646 commit 75c8fde

File tree

3 files changed

+46
-7
lines changed

3 files changed

+46
-7
lines changed

src/node/app/health.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as http from "http"
2+
import { HttpCode, HttpError } from "../../common/http"
3+
import { HttpProvider, HttpResponse, Route, Heart, HttpProviderOptions } from "../http"
4+
5+
/**
6+
* Check the heartbeat.
7+
*/
8+
export class HealthHttpProvider extends HttpProvider {
9+
public constructor(options: HttpProviderOptions, private readonly heart: Heart) {
10+
super(options)
11+
}
12+
13+
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
14+
if (!this.authenticated(request)) {
15+
if (this.isRoot(route)) {
16+
return { redirect: "/login", query: { to: route.fullPath } }
17+
}
18+
throw new HttpError("Unauthorized", HttpCode.Unauthorized)
19+
}
20+
21+
const result = {
22+
cache: false,
23+
mime: "application/json",
24+
content: {
25+
status: this.heart.alive() ? "alive" : "expired",
26+
lastHeartbeat: this.heart.lastHeartbeat,
27+
},
28+
}
29+
30+
return result
31+
}
32+
}

src/node/entry.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import * as cp from "child_process"
33
import { promises as fs } from "fs"
44
import http from "http"
55
import * as path from "path"
6-
import { CliMessage, OpenCommandPipeArgs } from "../../lib/vscode/src/vs/server/ipc"
6+
import { CliMessage } from "../../lib/vscode/src/vs/server/ipc"
77
import { plural } from "../common/util"
8+
import { HealthHttpProvider } from "./app/health"
89
import { LoginHttpProvider } from "./app/login"
910
import { ProxyHttpProvider } from "./app/proxy"
1011
import { StaticHttpProvider } from "./app/static"
@@ -80,6 +81,7 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise<void>
8081
httpServer.registerHttpProvider("/proxy", ProxyHttpProvider)
8182
httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, envPassword)
8283
httpServer.registerHttpProvider("/static", StaticHttpProvider)
84+
httpServer.registerHttpProvider("/healthz", HealthHttpProvider, httpServer.heart)
8385

8486
await loadPlugins(httpServer, args)
8587

src/node/http.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -396,23 +396,26 @@ export abstract class HttpProvider {
396396
export class Heart {
397397
private heartbeatTimer?: NodeJS.Timeout
398398
private heartbeatInterval = 60000
399-
private lastHeartbeat = 0
399+
public lastHeartbeat = 0
400400

401401
public constructor(private readonly heartbeatPath: string, private readonly isActive: () => Promise<boolean>) {}
402402

403+
public alive(): boolean {
404+
const now = Date.now()
405+
return now - this.lastHeartbeat < this.heartbeatInterval
406+
}
403407
/**
404408
* Write to the heartbeat file if we haven't already done so within the
405409
* timeout and start or reset a timer that keeps running as long as there is
406410
* activity. Failures are logged as warnings.
407411
*/
408412
public beat(): void {
409-
const now = Date.now()
410-
if (now - this.lastHeartbeat >= this.heartbeatInterval) {
413+
if (!this.alive()) {
411414
logger.trace("heartbeat")
412415
fs.outputFile(this.heartbeatPath, "").catch((error) => {
413416
logger.warn(error.message)
414417
})
415-
this.lastHeartbeat = now
418+
this.lastHeartbeat = Date.now()
416419
if (typeof this.heartbeatTimer !== "undefined") {
417420
clearTimeout(this.heartbeatTimer)
418421
}
@@ -457,7 +460,7 @@ export class HttpServer {
457460
private listenPromise: Promise<string | null> | undefined
458461
public readonly protocol: "http" | "https"
459462
private readonly providers = new Map<string, HttpProvider>()
460-
private readonly heart: Heart
463+
public readonly heart: Heart
461464
private readonly socketProvider = new SocketProxyProvider()
462465

463466
/**
@@ -602,8 +605,10 @@ export class HttpServer {
602605
}
603606

604607
private onRequest = async (request: http.IncomingMessage, response: http.ServerResponse): Promise<void> => {
605-
this.heart.beat()
606608
const route = this.parseUrl(request)
609+
if (route.providerBase !== "/healthz") {
610+
this.heart.beat()
611+
}
607612
const write = (payload: HttpResponse): void => {
608613
response.writeHead(payload.redirect ? HttpCode.Redirect : payload.code || HttpCode.Ok, {
609614
"Content-Type": payload.mime || getMediaMime(payload.filePath),

0 commit comments

Comments
 (0)