parent
de41646fc4
commit
75c8fdeed2
32
src/node/app/health.ts
Normal file
32
src/node/app/health.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import * as http from "http"
|
||||||
|
import { HttpCode, HttpError } from "../../common/http"
|
||||||
|
import { HttpProvider, HttpResponse, Route, Heart, HttpProviderOptions } from "../http"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the heartbeat.
|
||||||
|
*/
|
||||||
|
export class HealthHttpProvider extends HttpProvider {
|
||||||
|
public constructor(options: HttpProviderOptions, private readonly heart: Heart) {
|
||||||
|
super(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handleRequest(route: Route, request: http.IncomingMessage): Promise<HttpResponse> {
|
||||||
|
if (!this.authenticated(request)) {
|
||||||
|
if (this.isRoot(route)) {
|
||||||
|
return { redirect: "/login", query: { to: route.fullPath } }
|
||||||
|
}
|
||||||
|
throw new HttpError("Unauthorized", HttpCode.Unauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
cache: false,
|
||||||
|
mime: "application/json",
|
||||||
|
content: {
|
||||||
|
status: this.heart.alive() ? "alive" : "expired",
|
||||||
|
lastHeartbeat: this.heart.lastHeartbeat,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,9 @@ import * as cp from "child_process"
|
|||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
import http from "http"
|
import http from "http"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { CliMessage, OpenCommandPipeArgs } from "../../lib/vscode/src/vs/server/ipc"
|
import { CliMessage } from "../../lib/vscode/src/vs/server/ipc"
|
||||||
import { plural } from "../common/util"
|
import { plural } from "../common/util"
|
||||||
|
import { HealthHttpProvider } from "./app/health"
|
||||||
import { LoginHttpProvider } from "./app/login"
|
import { LoginHttpProvider } from "./app/login"
|
||||||
import { ProxyHttpProvider } from "./app/proxy"
|
import { ProxyHttpProvider } from "./app/proxy"
|
||||||
import { StaticHttpProvider } from "./app/static"
|
import { StaticHttpProvider } from "./app/static"
|
||||||
@ -80,6 +81,7 @@ const main = async (args: Args, cliArgs: Args, configArgs: Args): Promise<void>
|
|||||||
httpServer.registerHttpProvider("/proxy", ProxyHttpProvider)
|
httpServer.registerHttpProvider("/proxy", ProxyHttpProvider)
|
||||||
httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, envPassword)
|
httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, envPassword)
|
||||||
httpServer.registerHttpProvider("/static", StaticHttpProvider)
|
httpServer.registerHttpProvider("/static", StaticHttpProvider)
|
||||||
|
httpServer.registerHttpProvider("/healthz", HealthHttpProvider, httpServer.heart)
|
||||||
|
|
||||||
await loadPlugins(httpServer, args)
|
await loadPlugins(httpServer, args)
|
||||||
|
|
||||||
|
@ -396,23 +396,26 @@ export abstract class HttpProvider {
|
|||||||
export class Heart {
|
export class Heart {
|
||||||
private heartbeatTimer?: NodeJS.Timeout
|
private heartbeatTimer?: NodeJS.Timeout
|
||||||
private heartbeatInterval = 60000
|
private heartbeatInterval = 60000
|
||||||
private lastHeartbeat = 0
|
public lastHeartbeat = 0
|
||||||
|
|
||||||
public constructor(private readonly heartbeatPath: string, private readonly isActive: () => Promise<boolean>) {}
|
public constructor(private readonly heartbeatPath: string, private readonly isActive: () => Promise<boolean>) {}
|
||||||
|
|
||||||
|
public alive(): boolean {
|
||||||
|
const now = Date.now()
|
||||||
|
return now - this.lastHeartbeat < this.heartbeatInterval
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Write to the heartbeat file if we haven't already done so within the
|
* Write to the heartbeat file if we haven't already done so within the
|
||||||
* timeout and start or reset a timer that keeps running as long as there is
|
* timeout and start or reset a timer that keeps running as long as there is
|
||||||
* activity. Failures are logged as warnings.
|
* activity. Failures are logged as warnings.
|
||||||
*/
|
*/
|
||||||
public beat(): void {
|
public beat(): void {
|
||||||
const now = Date.now()
|
if (!this.alive()) {
|
||||||
if (now - this.lastHeartbeat >= this.heartbeatInterval) {
|
|
||||||
logger.trace("heartbeat")
|
logger.trace("heartbeat")
|
||||||
fs.outputFile(this.heartbeatPath, "").catch((error) => {
|
fs.outputFile(this.heartbeatPath, "").catch((error) => {
|
||||||
logger.warn(error.message)
|
logger.warn(error.message)
|
||||||
})
|
})
|
||||||
this.lastHeartbeat = now
|
this.lastHeartbeat = Date.now()
|
||||||
if (typeof this.heartbeatTimer !== "undefined") {
|
if (typeof this.heartbeatTimer !== "undefined") {
|
||||||
clearTimeout(this.heartbeatTimer)
|
clearTimeout(this.heartbeatTimer)
|
||||||
}
|
}
|
||||||
@ -457,7 +460,7 @@ export class HttpServer {
|
|||||||
private listenPromise: Promise<string | null> | undefined
|
private listenPromise: Promise<string | null> | undefined
|
||||||
public readonly protocol: "http" | "https"
|
public readonly protocol: "http" | "https"
|
||||||
private readonly providers = new Map<string, HttpProvider>()
|
private readonly providers = new Map<string, HttpProvider>()
|
||||||
private readonly heart: Heart
|
public readonly heart: Heart
|
||||||
private readonly socketProvider = new SocketProxyProvider()
|
private readonly socketProvider = new SocketProxyProvider()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -602,8 +605,10 @@ export class HttpServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onRequest = async (request: http.IncomingMessage, response: http.ServerResponse): Promise<void> => {
|
private onRequest = async (request: http.IncomingMessage, response: http.ServerResponse): Promise<void> => {
|
||||||
this.heart.beat()
|
|
||||||
const route = this.parseUrl(request)
|
const route = this.parseUrl(request)
|
||||||
|
if (route.providerBase !== "/healthz") {
|
||||||
|
this.heart.beat()
|
||||||
|
}
|
||||||
const write = (payload: HttpResponse): void => {
|
const write = (payload: HttpResponse): void => {
|
||||||
response.writeHead(payload.redirect ? HttpCode.Redirect : payload.code || HttpCode.Ok, {
|
response.writeHead(payload.redirect ? HttpCode.Redirect : payload.code || HttpCode.Ok, {
|
||||||
"Content-Type": payload.mime || getMediaMime(payload.filePath),
|
"Content-Type": payload.mime || getMediaMime(payload.filePath),
|
||||||
|
Reference in New Issue
Block a user