mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-11 01:01:13 +00:00
fix(gateway): move plugin HTTP routes before Control UI SPA catch-all
The Control UI handler (`handleControlUiHttpRequest`) acts as an SPA catch-all that matches every path, returning HTML for GET requests and 405 for other methods. Because it ran before `handlePluginRequest` in the request chain, any plugin HTTP route that did not live under `/plugins` or `/api` was unreachable — shadowed by the catch-all. Reorder the handlers so plugin routes are evaluated first. Core built-in routes (hooks, tools, Slack, Canvas, etc.) still take precedence because they are checked even earlier in the chain. Unmatched plugin paths continue to fall through to Control UI as before. Closes #31766
This commit is contained in:
committed by
Peter Steinberger
parent
718d418b32
commit
0e469f1257
@@ -587,6 +587,24 @@ export function createGatewayHttpServer(opts: {
|
||||
run: () => canvasHost.handleHttpRequest(req, res),
|
||||
});
|
||||
}
|
||||
// Plugin routes run before the Control UI SPA catch-all so explicitly
|
||||
// registered plugin endpoints stay reachable. Core built-in gateway
|
||||
// routes above still keep precedence on overlapping paths.
|
||||
requestStages.push(
|
||||
...buildPluginRequestStages({
|
||||
req,
|
||||
res,
|
||||
requestPath,
|
||||
pluginPathContext,
|
||||
handlePluginRequest,
|
||||
shouldEnforcePluginGatewayAuth,
|
||||
resolvedAuth,
|
||||
trustedProxies,
|
||||
allowRealIpFallback,
|
||||
rateLimiter,
|
||||
}),
|
||||
);
|
||||
|
||||
if (controlUiEnabled) {
|
||||
requestStages.push({
|
||||
name: "control-ui-avatar",
|
||||
@@ -606,22 +624,6 @@ export function createGatewayHttpServer(opts: {
|
||||
}),
|
||||
});
|
||||
}
|
||||
// Plugins run after built-in gateway routes so core surfaces keep
|
||||
// precedence on overlapping paths.
|
||||
requestStages.push(
|
||||
...buildPluginRequestStages({
|
||||
req,
|
||||
res,
|
||||
requestPath,
|
||||
pluginPathContext,
|
||||
handlePluginRequest,
|
||||
shouldEnforcePluginGatewayAuth,
|
||||
resolvedAuth,
|
||||
trustedProxies,
|
||||
allowRealIpFallback,
|
||||
rateLimiter,
|
||||
}),
|
||||
);
|
||||
|
||||
requestStages.push({
|
||||
name: "gateway-probes",
|
||||
|
||||
@@ -348,13 +348,13 @@ describe("gateway plugin HTTP auth boundary", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("does not let plugin handlers shadow control ui routes", async () => {
|
||||
test("plugin routes take priority over control ui catch-all", async () => {
|
||||
const handlePluginRequest = vi.fn(async (req: IncomingMessage, res: ServerResponse) => {
|
||||
const pathname = new URL(req.url ?? "/", "http://localhost").pathname;
|
||||
if (pathname === "/chat") {
|
||||
if (pathname === "/my-plugin/inbound") {
|
||||
res.statusCode = 200;
|
||||
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
res.end("plugin-shadow");
|
||||
res.end("plugin-handled");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -369,12 +369,34 @@ describe("gateway plugin HTTP auth boundary", () => {
|
||||
controlUiRoot: { kind: "missing" },
|
||||
handlePluginRequest,
|
||||
},
|
||||
run: async (server) => {
|
||||
const response = await sendRequest(server, { path: "/my-plugin/inbound" });
|
||||
|
||||
expect(response.res.statusCode).toBe(200);
|
||||
expect(response.getBody()).toContain("plugin-handled");
|
||||
expect(handlePluginRequest).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("unmatched plugin paths fall through to control ui", async () => {
|
||||
const handlePluginRequest = vi.fn(async () => false);
|
||||
|
||||
await withGatewayServer({
|
||||
prefix: "openclaw-plugin-http-control-ui-fallthrough-test-",
|
||||
resolvedAuth: AUTH_NONE,
|
||||
overrides: {
|
||||
controlUiEnabled: true,
|
||||
controlUiBasePath: "",
|
||||
controlUiRoot: { kind: "missing" },
|
||||
handlePluginRequest,
|
||||
},
|
||||
run: async (server) => {
|
||||
const response = await sendRequest(server, { path: "/chat" });
|
||||
|
||||
expect(handlePluginRequest).toHaveBeenCalledTimes(1);
|
||||
expect(response.res.statusCode).toBe(503);
|
||||
expect(response.getBody()).toContain("Control UI assets not found");
|
||||
expect(handlePluginRequest).not.toHaveBeenCalled();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user