Add /health endpoint to Java plugin and update health checks
Some checks are pending
Build Ghidra Plugin / build (push) Waiting to run
Some checks are pending
Build Ghidra Plugin / build (push) Waiting to run
New GET /health endpoint returns status, uptime, api_version, and loaded program without depending on program state. Lightweight enough for Docker HEALTHCHECK and monitoring probes. Python docker_health tool tries /health first, falls back to root endpoint for older plugin versions. Docker HEALTHCHECK updated to use /health instead of /.
This commit is contained in:
parent
14b2b575c8
commit
83949683ae
@ -147,6 +147,6 @@ ENV MCGHIDRA_MAXMEM=2G
|
||||
|
||||
# Healthcheck
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD curl -f http://localhost:${MCGHIDRA_PORT}/ || exit 1
|
||||
CMD curl -f http://localhost:${MCGHIDRA_PORT}/health || exit 1
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "mcghidra"
|
||||
version = "2026.3.2.1"
|
||||
version = "2026.3.6"
|
||||
description = "Reverse engineering bridge: multi-instance Ghidra plugin with HATEOAS REST API and MCP server for decompilation, analysis & binary manipulation"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
@ -52,6 +52,7 @@ public class MCGhidraPlugin extends Plugin implements ApplicationLevelPlugin {
|
||||
private HttpServer server;
|
||||
private int port;
|
||||
private boolean isBaseInstance = false;
|
||||
private long serverStartTimeMs;
|
||||
|
||||
/**
|
||||
* Constructor for MCGhidra Plugin.
|
||||
@ -109,6 +110,8 @@ public class MCGhidraPlugin extends Plugin implements ApplicationLevelPlugin {
|
||||
// Register Root Endpoint (should be last to include links to all other endpoints)
|
||||
registerRootEndpoint(server);
|
||||
|
||||
serverStartTimeMs = System.currentTimeMillis();
|
||||
|
||||
new Thread(() -> {
|
||||
server.start();
|
||||
Msg.info(this, "MCGhidra HTTP server started on port " + port);
|
||||
@ -184,6 +187,35 @@ public class MCGhidraPlugin extends Plugin implements ApplicationLevelPlugin {
|
||||
}
|
||||
});
|
||||
|
||||
// Health endpoint — lightweight, no program dependency
|
||||
server.createContext("/health", exchange -> {
|
||||
try {
|
||||
if ("GET".equals(exchange.getRequestMethod())) {
|
||||
long uptimeMs = System.currentTimeMillis() - serverStartTimeMs;
|
||||
Program program = getCurrentProgram();
|
||||
|
||||
Map<String, Object> healthData = new HashMap<>();
|
||||
healthData.put("status", "up");
|
||||
healthData.put("port", port);
|
||||
healthData.put("api_version", ApiConstants.API_VERSION);
|
||||
healthData.put("uptime_ms", uptimeMs);
|
||||
healthData.put("program", program != null ? program.getName() : null);
|
||||
|
||||
ResponseBuilder builder = new ResponseBuilder(exchange, port)
|
||||
.success(true)
|
||||
.result(healthData)
|
||||
.addLink("self", "/health")
|
||||
.addLink("root", "/");
|
||||
|
||||
HttpUtil.sendJsonResponse(exchange, builder.build(), 200, port);
|
||||
} else {
|
||||
HttpUtil.sendErrorResponse(exchange, 405, "Method Not Allowed", "METHOD_NOT_ALLOWED", port);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Msg.error(this, "Error handling /health", e);
|
||||
}
|
||||
});
|
||||
|
||||
// Info endpoint
|
||||
server.createContext("/info", exchange -> {
|
||||
try {
|
||||
@ -359,6 +391,7 @@ public class MCGhidraPlugin extends Plugin implements ApplicationLevelPlugin {
|
||||
.success(true)
|
||||
.result(rootData)
|
||||
.addLink("self", "/")
|
||||
.addLink("health", "/health")
|
||||
.addLink("info", "/info")
|
||||
.addLink("plugin-version", "/plugin-version")
|
||||
.addLink("projects", "/projects")
|
||||
|
||||
@ -889,10 +889,42 @@ class DockerMixin(MCGhidraMixinBase):
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
url = f"http://localhost:{port}/"
|
||||
health_url = f"http://localhost:{port}/health"
|
||||
root_url = f"http://localhost:{port}/"
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(url)
|
||||
# Try /health first (available in plugin v2.2+)
|
||||
req = urllib.request.Request(health_url)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as response:
|
||||
data = json_module.loads(response.read().decode())
|
||||
result = data.get("result", data)
|
||||
return {
|
||||
"healthy": True,
|
||||
"port": port,
|
||||
"api_version": result.get("api_version"),
|
||||
"program": result.get("program"),
|
||||
"uptime_ms": result.get("uptime_ms"),
|
||||
}
|
||||
except urllib.error.HTTPError:
|
||||
# /health not available — fall back to root endpoint (older plugin)
|
||||
pass
|
||||
except urllib.error.URLError as e:
|
||||
return {
|
||||
"healthy": False,
|
||||
"port": port,
|
||||
"error": str(e.reason),
|
||||
"message": "Container may still be starting or analyzing binary",
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"healthy": False,
|
||||
"port": port,
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
# Fallback: try root endpoint for older plugin versions
|
||||
try:
|
||||
req = urllib.request.Request(root_url)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as response:
|
||||
data = json_module.loads(response.read().decode())
|
||||
return {
|
||||
@ -900,14 +932,6 @@ class DockerMixin(MCGhidraMixinBase):
|
||||
"port": port,
|
||||
"api_version": data.get("api_version"),
|
||||
"program": data.get("program"),
|
||||
"file": data.get("file"),
|
||||
}
|
||||
except urllib.error.URLError as e:
|
||||
return {
|
||||
"healthy": False,
|
||||
"port": port,
|
||||
"error": str(e.reason),
|
||||
"message": "Container may still be starting or analyzing binary",
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user